Страницы

Поиск по вопросам

Показаны сообщения с ярлыком callback. Показать все сообщения
Показаны сообщения с ярлыком callback. Показать все сообщения

четверг, 19 марта 2020 г.

Синхронная обработка асинхронно накопленных данных. Теория.

#callback #javascript #асинхронность


Всем привет! Подскажите, пожалуйста, общий метод работы в следующем случае (и из
той же категории):
Предположим, есть набор id объектов, хранящихся в локальной БД мобильного приложения. 
var ids = [1, 3, 5].

Получать объект по его id я могу простым запросом к БД из транзакции:
for (var i in ids)
    transaction.executeSql( 'SELECT * FROM table WHERE id = '+i, [], self.querySuccess,
self.errorCB );

И вот мне в коллбэк приходят вытащенные объекты, мне надо их все сохранить в какой-нибудь
массив, и когда я получу все желаемые объекты, что-то с этим массивом сделать. Например,
на сервер пульнуть.
Как я делаю: перед началом цикла запоминаю сколько объектов я должен получить из
БД, в коллбэке веду счетчик пришедших объектов, и таким образом узнаю, когда я получил
все объекты.
Вопрос: нормально ли это, или велосипед? Может есть другие методы для решения подобных
задач?    


Ответы

Ответ 1



Посмотрите в сторону библиотеки Async.js, в ней есть множество методом, с помощью которых можно решить вашу проблему, например async.map async.map( ids, function(item, callback){ transaction.executeSql( 'SELECT * FROM table WHERE id = '+item, [], function(result){ // здесь наверно получаете результат выполнения запроса, его надо передать вторым параметром в callback callback(null/*error*/, result); }); }, function(err, resгlt){ // в result будет окончательный результат } );

понедельник, 24 февраля 2020 г.

Как правильно реализовать callback в java?

#java #android #callback


Я недавно начал для себя изучать Android и собственно java, до этого всегда писал
на nodeJS, и за долгое время очень привык к js.

И вот, в процессе изучения, настал момент, когда я решил написать один единственный
класс который будет отправлять запросы, и в результате ошибки или успеха будет выполнять
соответствующие методы класса из которого мы и шлем этот запрос.

Есть класс MainActivity в котором по клику на кнопку шлем запрос на сервер и ответ
вставляем в TextView. Сейчас я реализовал коллбэки таким образом:

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

protected void onCreate(Bundle savedInstanceState) {


sendButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        DoRequestClass doRequestClass = new DoRequestClass(){

                    @Override
                    public void onOK(JSONObject response) {
                        successReq(response);
                    }

                    @Override
                    public void onError(VolleyError error) {
                        errorReq(error);
                    }
                };
            doRequestClass.execute("http://myserver.local", MainActivity.this);
        }
    });


public void successReq(JSONObject response){
    txtDisplay.setText("Response => "+response.toString());
}

public void errorReq(VolleyError error){
    txtDisplay.setText("Error => "+error.toString());
}

// other methods


Такой вот для теста абстрактный класс DoRequestClass

public abstract class DoRequestClass implements DoRequestInterface {

    @Override
    public void execute(String url, Context context) {
        RequestQueue queue = Volley.newRequestQueue(context);

        JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET,
url, null, new Response.Listener() {

            @Override
            public void onResponse(JSONObject response) {
                DoRequestClass.this.onOK(response);
            }

        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                DoRequestClass.this.onError(error);
            }

        });

        queue.add(jsObjRequest);
    }
}


С интерфейсом DoRequestInterface

interface DoRequestInterface {
    void execute(String url, Context context);

    void onOK(JSONObject response);

    void onError(VolleyError error);
}


Все описанное выше работает, но можно ли как-то передать successReq и errorReq в
DoRequestClass таким образом, как бы мы это делали это на javascript, о то что я имею
ввиду, о желанном результате, смотрите как бы я хотел примерно чтоб они передавались:

sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DoRequestClass doRequestClass = new DoRequestClass();
                doRequestClass.setSuccessCallback(successReq);
                doRequestClass.setErrorCallback(errorReq);
                doRequestClass.execute("http://lessons.flexiblejs.ru/test.php", MainActivity.this);
            }
        });


Как вы поняли из кода выше, setSuccessCallback и setErrorCallback это методы которые
будут говорить классу DoRequestClass какие методы класса MainActivity нужно выполнить
после успешного или не успешного запроса.

Такое или подобное прекрасно работало бы в javascript'e, но такой java-код работать
не будет. Вопрос в том, как создать что-то такое или приближенное к этому? И возможно
ли вообще? Суть в том чтобы не писать лишний код и создавать лишнее методы типа как
в моем рабочем примере onOK и onError.

Прошу вас по возможности дать как можно более развернутый ответ с пояснениями, т.к.
мои познания в java совсем не велики.
    


Ответы

Ответ 1



Рефакторинг: MainActivity.java public class MainActivity extends AppCompatActivity implements View.OnClickListener, Response.Listener, Response.ErrorListener { protected void onCreate(Bundle savedInstanceState) { ((Button)findViewById(R.id.send)).setOnClickListener(this); } @Override public void onClick(View v){ switch(v.getId()){ case R.id.send: doRequestClass.execute("http://myserver.local", this, this, this); break; } } @Override public void onResponse(JSONObject response) { txtDisplay.setText("Response => "+response.toString()); } @Override public void onErrorResponse(VolleyError error) { txtDisplay.setText("Error => "+error.toString()); } } DoRequestClass.java public class DoRequestClass { @Override public void execute(String url, Context context, Response.Listener listener, Response.ErrorListener listenerError) { RequestQueue queue = Volley.newRequestQueue(context); JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, null, listener, listenerError); queue.add(jsObjRequest); } } На самом деле вы продублировали интерфейсы Response.Listener и Response.ErrorListener, просто раскрыв generic и объединив их в один. В приведенном примере это просто не нужно. Впрочем вы можете реализовать как у вас и было этот интерфейс, например обработать response и прокинуть его дальше: JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener(){ @Override public void onResponse(JSONObject response) { Log.d(TAG, "JSON:"+ response.toString()); listener.onResponse(response);// listener будет final } },listenerError); Для колбэков используется свой интерфейс (или несколько) и вносить в него метод execute смысла не очень много. В частности execute может быть в одном классе, а колбэк - в другом. ЗЫ В приведенном коде есть один недочет. Вы каждый раз создаете новую очередь запросов. Так делать не стоит, для всего приложения достаточно одной очереди. Вариантов когда нужно несколько очередей, притом фиксированное количество не так много, например когда реализуется работа с несколькими акаунтами, на разные сервера слать запросы в отдельных очередях... Поэтому в классе приложения (например) стоит создать синглтон для очереди и брать ее оттуда. Либо сделать DoRequestClass (назвав его RequestManager'ом, например) синглтоном с инициализированной один раз очередью... ЗЗЫ Еще перенес обработчик клика в активити, это по желанию "на вкус и цвет".

четверг, 23 января 2020 г.

Javascript: коллбэки, асинхронность и разделение кода

#javascript #callback #cordova #async


Здравствуйте.

Есть cordova приложение. Есть плагин, который я могу вызвать со стороны javascript,
чтобы получить данные извне, например, через такой враппер:

CordovaWrapper.getRawData(successCallback, errorCallback);


Данные, которые я получу из cordova (они придут как аргументы successCallback), я
бы хотел использовать в HTML приложении. 

Конечно, можно во всех местах, где нужны данные из successCallback, использовать
этот CordovaWrapper, чтобы получить данные и сразу же использовать:

CordovaWrapper.getRawData(function(JSONString) {
    var data = JSON.parse(JSONString)
    for (var i in data) {
        data[i].formattedValue = ''+data[i].value+'';
    }

    // здесь мы используем эти полученные и отформатированные данные

}, function(e) {
    console.log(e)
});


Но мне это не кажется хорошей идеей.
Кроме того, хорошо бы было закешировать распаршенный JSON с примененным к нему форматированием.

Я мало знаком с Javascript, но мне хотелось бы реализовать какой-нибудь провайдер
данных, к которому я смогу обращаться, чтобы получать закешированные и отформатированные
данные из cordova (используя этот гипотетический CordovaWrapper). И в случае работы
приложения в браузере, а не на девайсе, отдавать какие-нибудь захардкоженые данные.

    if (standaloneMode) {
        // какие-то тестовые данные
        return [{
            value: 'test1',
            options: [...],
        }, {
            value: 'test2',
            options: [...],
        }];
    } else {
        return dataFromCordova;
    }


Проблема в том, что получение данных - асинхронное.

Если делать асинхронно, получается коллбэк на коллбэке:

Объявление:

DataProvider.getData = function(callback) {
    var format = function (arr) {
        for (var i in arr) {
            arr[i].formattedValue = ''+arr[i].value+'';
        }
        return arr;
    };

    if (standaloneMode) {
        var testData = [{
            value: 'test1',
            options: [...],
        }, {
            value: 'test2',
            options: [...],
        }];

        callback(format(testData));
        return;
    }

    if (this.cache) {
        callback(this.cache);
    } else {
        var successCallback = function(JSONString) {
            data = JSON.parse(JSONString);
            callback(format(data));
        };
        CordovaWrapper.getRawData(successCallback, errorCallback);
    }
}


И использование:

DataProvider.getData(function (data) {
    //  здесь мы используем эти полученные и отформатированные данные
});


Были мысли загружать данные в провайдер до использования. Но, опять же, нет гарантий,
что момент, когда эти данные будут нужны, не наступит раньше, чем эти данные будут
получены из cordova.

Как правильно сделать? Нормально ли это, хотеть отдельный класс/объект, который будет
предоставлять данные? Нормально ли иметь столько коллбэков из-за асинхронности? Как
делать такого рода кеширование? Как не дублировать строчки вида

callback(format(data));


?
    


Ответы

Ответ 1



JavaScript это ивентво ориентированный язык, то есть лапша из колбеков это обычное дело. К сожелению текущая версия JavaScript не позволяет упростить работу с колбеками, и для этого вам необходимо подобрать библиотеку для упрощения логики работы с событиями. Замечу что если у вас в приложении действительно много колбеков то возможно стоит ознакомиться с Функциональным реактивным программированием. Вот очень мотивирующи доклад к изучению FRP. Но в большинстве случаев FRP будет излишним, и можно воспользоваться Promise (на самом деле это частный случай FRP). Промисы будут достепны в ES6 а пока вы можете воспользоваться реализацией промисов из какой-нибудь библиотеки например jquery Promise - это специальный объект который выпонит успешный callback или callback с ошибкой и гарантирует что один из этих колбеков будет выполнен и выполнен только один раз. На основании этих условий есть разные средства для комбинирования промисов, например метод when из jquery. Если вам нужно больше вариантов для комбинирования промисов, посмотрите библиотеку Q. По поводу вашего вопроса с кешированием ответа я приведу пример с использованием jquery, в других библотеках решение не должно сильно отличаться. В jquery есть класс Deferred который помогает создавать промисы. Вам нужно создать объект Deferred var deferred = new $.Deferred() например в вашем врапере и вернуть промис deferred.promise() В вашем врапере в момент получения данных необходимо вызвать метод resolve если получение данных было успешным или reject в противном случае. У обекта который вы получите в результате метода deferred.promise() будут методы done и fail которые принимают callback. Обратите внимание что колбек вызовится в любом случае, даже если получение данных произошло до добавления колбеков в методы done и fail. Таким образом вы можете создать 1 объект Deferred для каждого запроса в функции получения данных и возвращать промис каждый раз при обрашении к функции. В итоге вы получите кеширование данных и избавитесь от лапшы колбеков. Примеры Deferred Статья про промисы

воскресенье, 29 декабря 2019 г.

Вызов C# функции из javascript

#javascript #c_sharp #wpf #net #callback


У меня есть десктопное приложение на WPF, в котором лежит . В него грузится
один сервис с сайта, для отображения и работы с картами. В зависимости от логики я
через Invoke вызываю там разные javascript функции. 

Что делать, если мне надо подписатся на callback событие? Как из javascript вызвать
функцию на C#? Все что удалось нагуглить написано для использования в ASP.Net/Silverlight,
но как сделать это в обычном десктоп приложении?
    


Ответы

Ответ 1



Самое простое - сделать на С# ComVisible класс и обращаться к нему из JavaScript. Этот класс уже будет обращаться к приложению (через Application.Current).

Ответ 2



Что бы вызвать C# функцию из JavaScript, надо сначала установить ObjectForScripting (свойство WebBrowser класса) видимому COM-объекту: [ComVisible(true)] public class ScriptManager { public ScriptManager() { } public void MyFunc() { MessageBox.Show("MyFunc has been called"); } } В общем, надо создать WebBrowser контрол и свойству ObjectForScripting установить созданный ScriptManager, вот так: WebBrowser wb = new WebBrowser(); wb.ObjectForScripting = new ScriptManager(); wb.Navigate("location here"); Потом, в JavaScript можно вызывать созданный метод так: window.external.MyFunc();

пятница, 20 декабря 2019 г.

CallBack функция как член класса

#callback #cpp


Возможно ли использовать CallBack вызовы при написании своих классов (на С++, под
OS Windows)?
К примеру реализовать таймер при использовании Callback в своём классе!?
  SetTimer(NULL, 0, 1000, (TIMERPROC) TimerProc)
  void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
    


Ответы

Ответ 1



В общем виде передать указатель на нестатический, наверное, не получится. Но конкретно описываемую Вами задачу можно решить, например, так. Экземпляры классов, которые хотят получать уведомления от таймера регистрируются в некотором словаре, получая при этом идентификатор таймера. Обработчик таймера находит получателя по идентификатору таймера и переадресует вызов ему. Грубый пример: static int s_counter = 0; map s_receivers; class ITimerReciever { public: virtual void OnTimer() = 0; } static void AdviseToTimer(UINT elapse, ITimerReceiver * pReceiver) { receivers[++s_counter] = pReceiver; SetTimer(NULL, s_counter, elapse, (TIMERPROC) TimerProc); } void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) { receivers[idEvent]->OnTimer(); }

Ответ 2



@reije, хочу конкретный пример что хотите сделать. В общем случае - это плохая идея. С другой стороны, запросто можно получить адрес static-функции-члена класса, но есть нюанс, связанный с тем, что статическая ф-ция может получить доступ только к статическим членам класса. Т.е. по сути мы всего лишь засовываем нашу чудесную обычную функцию в namespace класса. Другой вопрос, что static ф-ция может получить доступ к каждому объекту своего типа, но это придется реализовать самостоятельно. Прекрасный пример использования возможностей С++ для реализации callback'ов предложен на SO. Используются шаблоны std::function и std::bind В качестве обходных путей предлагается реализовать: паттерн observer систему событий и их обработчиков

Ответ 3



Если я правильно понял вопрос, требуется использовать методы класса как функции обратного вызова. Это стандартная вещь: class Example { public: static void member(); }; int main() { signal(SIGINT, Example::member); }

Ответ 4



Да конечно, можно.

суббота, 30 ноября 2019 г.

Передача функции в функцию в javascript

#javascript #callback


Есть такой пример в javascript:



var fruits = ['apple', 'banana', 'orange', 'grapes', 'pear', 'passionfruit'];

// The three values on the callback function are:
// element - The element being traversed
// index - The current index of the item in the array starting at 0
// array - The array being traversed (probably mostly irrelevant)

fruits.forEach(function(element, index, array) {
   console.log(index, element); 
});




в метод forEach передается функция. Я так понимаю, что в методе forEach где-то эта
передаваемая функция вызывается. Чтобы ее вызвать, нужно ей передать 3 аргумента (желательно).
Люди, написавшие код этой функции как-то там перебирают элементы массива fruits и вызывают
мою callback функцию, передавая ей все эти три параметра. Каким образом они знают что
на каком месте стоит? Ведь по сути я могу написать не 

function(element, index,array)


а

function(myelement, myindex,myarray)


т.е. название параметров сменить и все равно будет работать. Получается, что элементы,
передаваемые в функцию "привязаны" к месту параметра - 1, 2, 3 по счету. Так? Или где-то
я ошибаюсь. Подскажите, пожалуйста?

Вынесу вопрос отдельно: 

параметры, передаваемые в колбэк функцию "привязаны" к месту параметра?
    


Ответы

Ответ 1



Аргументы, переданные в функцию - это, по сути, массив arguments[]. Ваше предположение, что порядок имеет значение, верно. Магия JavaScript: arguments (Хабр).

Ответ 2



Люди, написавшие код этой функции как-то там перебирают элементы массива fruits и вызывают мою callback функцию, передавая ей все эти три параметра. Каким образом они знают что на каком месте стоит? Все очень просто - они не знают как описана функция, которая передается в качестве коллбэка. Они просто берут ее и вызывают передавая ей параметры: element, index, sourse_array. Кроме этого, у коллбэка может быть указан контекст (значение this) внутри функции. Так как разработчики forEach не знают, какую функцию им передадут, они просто вызывают любой коллбэк в следующем виде callback(element,index,sourse_array) Либо дополнительно устанавливая this, что эквивалентно вызову call callback.call(thisArg,element,index,sourse_array) При этом сама функция коллбэка может быть объявлена с тремя параметрами, с двумя, с четырьмя, вообще без параметров и т.д. Подробнее описание forEach в справке

Ответ 3



Вы аргументы можете назвать как угодно, например fruits.forEach(function(first, second, third) { // first всегда будет итерируемым елементом масива // second всегда будет его номером // third всегда будет масивом по которому осуществляется итерация // например даст true console.log(third[second] === first) }); Кроме того, все аргументы являются необязательными (хотя forEach без первого елемента теряет смысл) Более подробно можно почитать тут. Там же в конце есть пример реализации функции forEach()

понедельник, 15 июля 2019 г.

Callback из MediaPlayer

Есть MediaPlayer и аудиозапись, которую нужно воспроизвести, в определенные моменты времени во время воспроизведения аудиозаписи мне нужно переключать слайды в ViewPager, как получить CallBack из MediaPlayer, что определенный момент времени наступил? Либо возможно посоветуете какие-то другие варианты.


Ответ

Решение, которое мне помогло:
CountDownTimer timer; int timeStampIterator; int[] timeStamp = new int[5]; // метки с временем
void play() { mPlayer.start(); timer = new CountDownTimer(mPlayer.getDuration() - mPlayer.getCurrentPosition(), 1000) { @Override public void onTick(long millisUntilFinished) { int timeSpends = mPlayer.getCurrentPosition(); if (timeSpends == timeStamp[timeStampIterator]) { mPager.setCurrentItem(LabelPosition.get(timeStampIterator)))); timeStampIterator++; } }
@Override public void onFinish() {
} }.start(); }
void pause() { mPlayer.pause(); timer.cancel(); timer = null; }
void stop() { mPlayer.stop(); timer.cancel(); timer = null; timeStampIterator = 0; }

воскресенье, 7 июля 2019 г.

Callback метода класса из другого класса без использования static, или с использованием только одного static объекта в С++ 0х14

Всем доброго времени суток, столкнулся с такой проблемой.
Есть чисто абстрактный класс UART который имеет следующий набор виртуальных методов :
virtual int8_t initialize(char* port_name, int baud_rate, Parity parity, int dataBits, StopBits stop_bits) = 0; virtual int8_t EDataReceived(void(*delegate)(char* data, const int16_t & length)) = 0; virtual void transmitDataToUart(char* data, const int16_t & length) = 0;
и есть 5 различных платформ на которых есть своя реализация класса, но все они унаследованы от класса UART, например class UART_Linux: UART. Это нужно для того что бы единожды написанный драйвер под микросхему SIM666 или SIM777, принимая в качестве параметра конкретный объект наследника UART мог бы работать на всех пяти платформах без изменений в коде. Когда на аппаратный uart приходят данные то класс UART должен делать колбэк вызов и передавать принятые данные с помощью функции, которая регистрируется в методе
virtual int8_t EDataReceived(void(*delegate)(char* data, const int16_t & length)).
Эта регистрация происходит внутри конструктора классов SIM666 и SIM777, например:
Sim666::Sim666(Uart& in_uart){ in_uart.EDataReceived(ReceiveDataFromUART); }
сам класс UART_Linux: UART не должен не чего знать о классе SIM666 т. к. это однажды написанный универсальный класс а SIM666 будет написан допустим спустя 5 лет после этого. Проблема заключается в том что метод ReceiveDataFromUART который является частью SIM666 должен обязательно быть статическим. Статическим его сделать не получается т. к. внутри этого метода должны использоваться не статик поля и свойства класса (т. е. конкретные экземпляры объекта).
Что бы решить мою проблему можно решить хотя бы один из трёх вопросов:
1) Реализовать возможность вызова внутри UART_Linux не статик стороннего метода.
2) Сделать метод Sim666.ReceiveDataFromUART статическим но реализовать в нём возможность обращения к не статическим полям класса (т. е. на уровне объекта).
3) Можно создать статический объект класса SIM666, тогда если регистрировать метода происходит снаружи класса SIM666 то всё работает, но внутри метода
void getPeriphery(const sd::Uart& in_uart) { in_uart.EDataReceived(ReceiveDataFromUART); };
компилятор не понимает что объект сейчас статический и данный вызов является корректным. Можно ли как то сообщить об этом компилятору? Что то типа как привести ReceiveDataFromUART к статику?
Стандарт языка С++ 0х14 , компиляторы и платформы как вы понимаете различны. Из стандартных библиотек можно использовать только стандартную библиотеку С.


Ответ

Если в Вашем случае использовать std::function, то это может покрыть все необходимые варианты использования
#include #include
typedef std::function Callback;
class MyClass { public: MyClass(Callback _cb): //регистрация колбэк функции в конструкторе m_callback(_cb) {
}
void invokeSomeFunc() { //много каких-то действий //... m_callback(0, 1); }
private: std::function m_callback; };
void func1(char* data, const int & length) { std::cout << __FUNCSIG__ << std::endl; }
class AnotherClass { public: static void staticFunc(char* data, const int & length) { std::cout << __FUNCSIG__ << std::endl; } };
class AnotherClass2 { public: void notStaticFunc(char* data, const int & length) { std::cout << __FUNCSIG__ << std::endl; } };
int main(int argc, char *argv[]) { //вариант использования с свободной функцией { MyClass someObj(func1); someObj.invokeSomeFunc(); }
//вариант использования с статической функцией класса { MyClass someObj(&AnotherClass::staticFunc); someObj.invokeSomeFunc(); }
//вариант использования с не статической функцией класса //тут нужно понимать, что объект someAnotherObj не должен быть разрушен на момент // вызова колбек функции, если invokeSomeFunc - асинхронная, то можно создать объект в куче. { AnotherClass2 someAnotherObj;
auto fn = [&someAnotherObj](char* data, const int & length) { someAnotherObj.notStaticFunc(data, length); };
MyClass someObj(fn); someObj.invokeSomeFunc(); }
{ AnotherClass2 *someAnotherObj = new AnotherClass2();
auto fn = [someAnotherObj](char* data, const int & length) { someAnotherObj->notStaticFunc(data, length); delete someAnotherObj; };
MyClass someObj(fn); someObj.invokeSomeFunc(); }
return 0; }
Если не хочется использовать std::function, можно в колбэк функцию добавить еще один параметр типа void* для пользовательских данных:
#include #include
typedef void(*Callback)(char* data, const int & length, void *_userData);
class MyClass { public: MyClass(Callback _cb, void *_userData = 0): m_callback(_cb), m_userData(_userData) {
}
void invokeSomeFunc() { //много каких-то действий //... m_callback(0, 1, m_userData); }
private: Callback m_callback; void *m_userData; };
void func1(char* data, const int & length, void *userData) { std::cout << __FUNCSIG__ << std::endl; }
class AnotherClass { public: static void staticFunc(char* data, const int & length, void *userData) { std::cout << __FUNCSIG__ << std::endl; } };
class AnotherClass2 { public: void notStaticFunc(char* data, const int & length) { std::cout << __FUNCSIG__ << std::endl; } };
void funcToCallObjectMethod(char* data, const int & length, void *userData) { AnotherClass2 *obj = static_cast(userData); obj->notStaticFunc(data, length); delete obj; }
int main(int argc, char *argv[]) { //вариант использования с свободной функцией { MyClass someObj(func1); someObj.invokeSomeFunc(); }
//вариант использования с статической функцией класса { MyClass someObj(&AnotherClass::staticFunc); someObj.invokeSomeFunc(); }
//вариант использования с не статической функцией класса //тут нужно понимать, что объект someAnotherObj не должен быть разрушен на момент // вызова колбек функции, если invokeSomeFunc - асинхронная, то можно создать объект в куче. { AnotherClass2 someAnotherObj;
auto fn = [](char* data, const int & length, void *userData) { AnotherClass2 *obj = static_cast(userData); obj->notStaticFunc(data, length); };
MyClass someObj(fn, &someAnotherObj); someObj.invokeSomeFunc(); }
{ AnotherClass2 *someAnotherObj = new AnotherClass2();
auto fn = [](char* data, const int & length, void *userData) { AnotherClass2 *obj = static_cast(userData); obj->notStaticFunc(data, length); delete obj; };
MyClass someObj(fn, someAnotherObj); someObj.invokeSomeFunc(); }
{ AnotherClass2 *someAnotherObj = new AnotherClass2();
MyClass someObj(funcToCallObjectMethod, someAnotherObj); someObj.invokeSomeFunc(); }
return 0; }
Как третий вариант, можно использовать реализацию паттерна Observer, пример уже приводить не буду.
IMHO, первый вариант самый удобный и экономит время в разработке.

пятница, 7 июня 2019 г.

Синхронная обработка асинхронно накопленных данных. Теория.

Всем привет! Подскажите, пожалуйста, общий метод работы в следующем случае (и из той же категории): Предположим, есть набор id объектов, хранящихся в локальной БД мобильного приложения. var ids = [1, 3, 5]. Получать объект по его id я могу простым запросом к БД из транзакции: for (var i in ids) transaction.executeSql( 'SELECT * FROM table WHERE id = '+i, [], self.querySuccess, self.errorCB ); И вот мне в коллбэк приходят вытащенные объекты, мне надо их все сохранить в какой-нибудь массив, и когда я получу все желаемые объекты, что-то с этим массивом сделать. Например, на сервер пульнуть. Как я делаю: перед началом цикла запоминаю сколько объектов я должен получить из БД, в коллбэке веду счетчик пришедших объектов, и таким образом узнаю, когда я получил все объекты. Вопрос: нормально ли это, или велосипед? Может есть другие методы для решения подобных задач?


Ответ

Посмотрите в сторону библиотеки Async.js, в ней есть множество методом, с помощью которых можно решить вашу проблему, например async.map async.map( ids, function(item, callback){ transaction.executeSql( 'SELECT * FROM table WHERE id = '+item, [], function(result){ // здесь наверно получаете результат выполнения запроса, его надо передать вторым параметром в callback callback(null/*error*/, result); }); }, function(err, resгlt){ // в result будет окончательный результат } );

вторник, 30 апреля 2019 г.

Как правильно реализовать callback в java?

Я недавно начал для себя изучать Android и собственно java, до этого всегда писал на nodeJS, и за долгое время очень привык к js.
И вот, в процессе изучения, настал момент, когда я решил написать один единственный класс который будет отправлять запросы, и в результате ошибки или успеха будет выполнять соответствующие методы класса из которого мы и шлем этот запрос.
Есть класс MainActivity в котором по клику на кнопку шлем запрос на сервер и ответ вставляем в TextView. Сейчас я реализовал коллбэки таким образом:
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
protected void onCreate(Bundle savedInstanceState) {
sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { DoRequestClass doRequestClass = new DoRequestClass(){
@Override public void onOK(JSONObject response) { successReq(response); }
@Override public void onError(VolleyError error) { errorReq(error); } }; doRequestClass.execute("http://myserver.local", MainActivity.this); } });
public void successReq(JSONObject response){ txtDisplay.setText("Response => "+response.toString()); }
public void errorReq(VolleyError error){ txtDisplay.setText("Error => "+error.toString()); }
// other methods
Такой вот для теста абстрактный класс DoRequestClass
public abstract class DoRequestClass implements DoRequestInterface {
@Override public void execute(String url, Context context) { RequestQueue queue = Volley.newRequestQueue(context);
JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener() {
@Override public void onResponse(JSONObject response) { DoRequestClass.this.onOK(response); }
}, new Response.ErrorListener() {
@Override public void onErrorResponse(VolleyError error) { DoRequestClass.this.onError(error); }
});
queue.add(jsObjRequest); } }
С интерфейсом DoRequestInterface
interface DoRequestInterface { void execute(String url, Context context);
void onOK(JSONObject response);
void onError(VolleyError error); }
Все описанное выше работает, но можно ли как-то передать successReq и errorReq в DoRequestClass таким образом, как бы мы это делали это на javascript, о то что я имею ввиду, о желанном результате, смотрите как бы я хотел примерно чтоб они передавались:
sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { DoRequestClass doRequestClass = new DoRequestClass(); doRequestClass.setSuccessCallback(successReq); doRequestClass.setErrorCallback(errorReq); doRequestClass.execute("http://lessons.flexiblejs.ru/test.php", MainActivity.this); } });
Как вы поняли из кода выше, setSuccessCallback и setErrorCallback это методы которые будут говорить классу DoRequestClass какие методы класса MainActivity нужно выполнить после успешного или не успешного запроса.
Такое или подобное прекрасно работало бы в javascript'e, но такой java-код работать не будет. Вопрос в том, как создать что-то такое или приближенное к этому? И возможно ли вообще? Суть в том чтобы не писать лишний код и создавать лишнее методы типа как в моем рабочем примере onOK и onError
Прошу вас по возможности дать как можно более развернутый ответ с пояснениями, т.к. мои познания в java совсем не велики.


Ответ

Рефакторинг:
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener, Response.Listener, Response.ErrorListener {
protected void onCreate(Bundle savedInstanceState) { ((Button)findViewById(R.id.send)).setOnClickListener(this); }
@Override public void onClick(View v){ switch(v.getId()){ case R.id.send: doRequestClass.execute("http://myserver.local", this, this, this); break; } }
@Override public void onResponse(JSONObject response) { txtDisplay.setText("Response => "+response.toString()); }
@Override public void onErrorResponse(VolleyError error) { txtDisplay.setText("Error => "+error.toString()); } }
DoRequestClass.java
public class DoRequestClass {
@Override public void execute(String url, Context context, Response.Listener listener, Response.ErrorListener listenerError) { RequestQueue queue = Volley.newRequestQueue(context); JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, null, listener, listenerError); queue.add(jsObjRequest); } }
На самом деле вы продублировали интерфейсы Response.Listener и Response.ErrorListener, просто раскрыв generic и объединив их в один. В приведенном примере это просто не нужно. Впрочем вы можете реализовать как у вас и было этот интерфейс, например обработать response и прокинуть его дальше:
JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener(){ @Override public void onResponse(JSONObject response) { Log.d(TAG, "JSON:"+ response.toString()); listener.onResponse(response);// listener будет final } },listenerError);
Для колбэков используется свой интерфейс (или несколько) и вносить в него метод execute смысла не очень много. В частности execute может быть в одном классе, а колбэк - в другом.
ЗЫ В приведенном коде есть один недочет. Вы каждый раз создаете новую очередь запросов. Так делать не стоит, для всего приложения достаточно одной очереди. Вариантов когда нужно несколько очередей, притом фиксированное количество не так много, например когда реализуется работа с несколькими акаунтами, на разные сервера слать запросы в отдельных очередях... Поэтому в классе приложения (например) стоит создать синглтон для очереди и брать ее оттуда. Либо сделать DoRequestClass (назвав его RequestManager'ом, например) синглтоном с инициализированной один раз очередью...
ЗЗЫ Еще перенес обработчик клика в активити, это по желанию "на вкус и цвет".

суббота, 16 марта 2019 г.

Javascript: коллбэки, асинхронность и разделение кода

Здравствуйте.
Есть cordova приложение. Есть плагин, который я могу вызвать со стороны javascript, чтобы получить данные извне, например, через такой враппер:
CordovaWrapper.getRawData(successCallback, errorCallback);
Данные, которые я получу из cordova (они придут как аргументы successCallback), я бы хотел использовать в HTML приложении.
Конечно, можно во всех местах, где нужны данные из successCallback, использовать этот CordovaWrapper, чтобы получить данные и сразу же использовать:
CordovaWrapper.getRawData(function(JSONString) { var data = JSON.parse(JSONString) for (var i in data) { data[i].formattedValue = ''+data[i].value+''; }
// здесь мы используем эти полученные и отформатированные данные
}, function(e) { console.log(e) });
Но мне это не кажется хорошей идеей. Кроме того, хорошо бы было закешировать распаршенный JSON с примененным к нему форматированием.
Я мало знаком с Javascript, но мне хотелось бы реализовать какой-нибудь провайдер данных, к которому я смогу обращаться, чтобы получать закешированные и отформатированные данные из cordova (используя этот гипотетический CordovaWrapper). И в случае работы приложения в браузере, а не на девайсе, отдавать какие-нибудь захардкоженые данные.
if (standaloneMode) { // какие-то тестовые данные return [{ value: 'test1', options: [...], }, { value: 'test2', options: [...], }]; } else { return dataFromCordova; }
Проблема в том, что получение данных - асинхронное.
Если делать асинхронно, получается коллбэк на коллбэке:
Объявление:
DataProvider.getData = function(callback) { var format = function (arr) { for (var i in arr) { arr[i].formattedValue = ''+arr[i].value+''; } return arr; };
if (standaloneMode) { var testData = [{ value: 'test1', options: [...], }, { value: 'test2', options: [...], }];
callback(format(testData)); return; }
if (this.cache) { callback(this.cache); } else { var successCallback = function(JSONString) { data = JSON.parse(JSONString); callback(format(data)); }; CordovaWrapper.getRawData(successCallback, errorCallback); } }
И использование:
DataProvider.getData(function (data) { // здесь мы используем эти полученные и отформатированные данные });
Были мысли загружать данные в провайдер до использования. Но, опять же, нет гарантий, что момент, когда эти данные будут нужны, не наступит раньше, чем эти данные будут получены из cordova.
Как правильно сделать? Нормально ли это, хотеть отдельный класс/объект, который будет предоставлять данные? Нормально ли иметь столько коллбэков из-за асинхронности? Как делать такого рода кеширование? Как не дублировать строчки вида
callback(format(data));
?


Ответ

JavaScript это ивентво ориентированный язык, то есть лапша из колбеков это обычное дело. К сожелению текущая версия JavaScript не позволяет упростить работу с колбеками, и для этого вам необходимо подобрать библиотеку для упрощения логики работы с событиями.
Замечу что если у вас в приложении действительно много колбеков то возможно стоит ознакомиться с Функциональным реактивным программированием. Вот очень мотивирующи доклад к изучению FRP.
Но в большинстве случаев FRP будет излишним, и можно воспользоваться Promise (на самом деле это частный случай FRP). Промисы будут достепны в ES6 а пока вы можете воспользоваться реализацией промисов из какой-нибудь библиотеки например jquery
Promise - это специальный объект который выпонит успешный callback или callback с ошибкой и гарантирует что один из этих колбеков будет выполнен и выполнен только один раз. На основании этих условий есть разные средства для комбинирования промисов, например метод when из jquery. Если вам нужно больше вариантов для комбинирования промисов, посмотрите библиотеку Q
По поводу вашего вопроса с кешированием ответа я приведу пример с использованием jquery, в других библотеках решение не должно сильно отличаться.
В jquery есть класс Deferred который помогает создавать промисы.
Вам нужно создать объект Deferred var deferred = new $.Deferred() например в вашем врапере и вернуть промис deferred.promise() В вашем врапере в момент получения данных необходимо вызвать метод resolve если получение данных было успешным или reject в противном случае. У обекта который вы получите в результате метода deferred.promise() будут методы done и fail которые принимают callback. Обратите внимание что колбек вызовится в любом случае, даже если получение данных произошло до добавления колбеков в методы done и fail
Таким образом вы можете создать 1 объект Deferred для каждого запроса в функции получения данных и возвращать промис каждый раз при обрашении к функции. В итоге вы получите кеширование данных и избавитесь от лапшы колбеков.
Примеры Deferred
Статья про промисы

среда, 12 декабря 2018 г.

Вызов C# функции из javascript

У меня есть десктопное приложение на WPF, в котором лежит . В него грузится один сервис с сайта, для отображения и работы с картами. В зависимости от логики я через Invoke вызываю там разные javascript функции.
Что делать, если мне надо подписатся на callback событие? Как из javascript вызвать функцию на C#? Все что удалось нагуглить написано для использования в ASP.Net/Silverlight, но как сделать это в обычном десктоп приложении?


Ответ

Самое простое - сделать на С# ComVisible класс и обращаться к нему из JavaScript. Этот класс уже будет обращаться к приложению (через Application.Current).

пятница, 9 ноября 2018 г.

CallBack функция как член класса

Возможно ли использовать CallBack вызовы при написании своих классов (на С++, под OS Windows)? К примеру реализовать таймер при использовании Callback в своём классе!? SetTimer(NULL, 0, 1000, (TIMERPROC) TimerProc) void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)


Ответ

В общем виде передать указатель на нестатический, наверное, не получится. Но конкретно описываемую Вами задачу можно решить, например, так. Экземпляры классов, которые хотят получать уведомления от таймера регистрируются в некотором словаре, получая при этом идентификатор таймера. Обработчик таймера находит получателя по идентификатору таймера и переадресует вызов ему. Грубый пример: static int s_counter = 0; map s_receivers; class ITimerReciever { public: virtual void OnTimer() = 0; }
static void AdviseToTimer(UINT elapse, ITimerReceiver * pReceiver) { receivers[++s_counter] = pReceiver; SetTimer(NULL, s_counter, elapse, (TIMERPROC) TimerProc); }
void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) { receivers[idEvent]->OnTimer(); }

четверг, 4 октября 2018 г.

Передача функции в функцию в javascript

Есть такой пример в javascript:
var fruits = ['apple', 'banana', 'orange', 'grapes', 'pear', 'passionfruit']; // The three values on the callback function are: // element - The element being traversed // index - The current index of the item in the array starting at 0 // array - The array being traversed (probably mostly irrelevant) fruits.forEach(function(element, index, array) { console.log(index, element); });
в метод forEach передается функция. Я так понимаю, что в методе forEach где-то эта передаваемая функция вызывается. Чтобы ее вызвать, нужно ей передать 3 аргумента (желательно). Люди, написавшие код этой функции как-то там перебирают элементы массива fruits и вызывают мою callback функцию, передавая ей все эти три параметра. Каким образом они знают что на каком месте стоит? Ведь по сути я могу написать не
function(element, index,array)
а
function(myelement, myindex,myarray)
т.е. название параметров сменить и все равно будет работать. Получается, что элементы, передаваемые в функцию "привязаны" к месту параметра - 1, 2, 3 по счету. Так? Или где-то я ошибаюсь. Подскажите, пожалуйста?
Вынесу вопрос отдельно:
параметры, передаваемые в колбэк функцию "привязаны" к месту параметра?


Ответ

Аргументы, переданные в функцию - это, по сути, массив arguments[]. Ваше предположение, что порядок имеет значение, верно.
Магия JavaScript: arguments (Хабр).