Страницы

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

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

Как последовательно вызвать асинхронную функцию с коллбеками?

#javascript #nodejs #javascript_faq


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

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

Вот так получается одновременно:



function doSmth(x, callback) {
  setTimeout(callback, Math.random() * 100 | 0, null, x);
}

var data = [1, 2, 3, 4, 5, 6, 7, 8];

for (var x of data) {
  doSmth(x, function (err, res) {
    console.log(err || res);
  });
}
.as-console-wrapper.as-console-wrapper { max-height: 100vh }



    


Ответы

Ответ 1



Надо делать вызов внутри коллбека. Для этого придётся переписать цикл на рекурсию (ну или не совсем рекурсию): function doSmth(x, callback) { setTimeout(callback, Math.random() * 100 | 0, null, x); } var data = [1, 2, 3, 4, 5, 6, 7, 8]; (function go(i) { // <================== рекурсивная функция вместо цикла if (i >= data.length) { return; // <======================== выход, когда массив закончился } doSmth(data[i], function (err, res) { console.log(err || res); go(i + 1); // <===================== рекурсивный вызов из коллбэка }); })(0); .as-console-wrapper.as-console-wrapper { max-height: 100vh } И остаётся ещё один момент - обычно нам бы надо узнать, когда завершилась обработка. Для этого функция go вместо return может вызвать другой коллбэк из вызывающей функции: function doSmth(x, callback) { setTimeout(callback, Math.random() * 100 | 0, null, x); } function process(data, callback) { (function go(i) { // <================== рекурсивная функция вместо цикла if (i >= data.length) { return callback(null, null); // <=== return позволяет избежать рекурсивного вызова } doSmth(data[i], function (err, res) { if (err) { return callback(err, null); // <== прекращаем дальнейшую обработку } console.log(res); go(i + 1); // <===================== рекурсивный вызов из коллбэка }); })(0); } var data = [1, 2, 3, 4, 5, 6, 7, 8]; process(data, function (err, res) { console.log(err || "Готово!"); }); .as-console-wrapper.as-console-wrapper { max-height: 100vh }

Ответ 2



В последних версиях языка есть механизмы, позволяющие писать более понятный код чем вариант с рекурсией. Для начала, надо преобразовать функцию так, чтобы она возвращала обещание (Promise). Переписывать ее для этого не нужно: достаточно обернуть. function doSmth1(x) { return new Promise((resolve, reject) => { doSmth(x, (err, result) => { if (err) reject(err); else resolve(result); }); }); } Также можно один раз написать функцию, которая будет оборачивать любую заданную (или взять ее из какой-нибудь библиотеки): function promisify(f) { return function(...args) { return Promise((resolve, reject) => { f.call(this, ...args, (err, result) => { if (err) reject(err); else resolve(result); }); }); } } var doSmth1 = promisify(doSmth); Это позволяет использовать асинхронные функции из стандарта ES2017: async function foo() { var doSmth1 = promisify(doSmth); for (var x of data) { console.log(await doSmth1(x)); } } Если по какой-то причине нет желания выделять целую функцию под асинхронный код - нет проблем вызвать анонимную функцию на месте: (async () => { var doSmth1 = promisify(doSmth); for (var x of data) { console.log(await doSmth1(x)); } })(); PS когда вы пишите свои асинхронные функции, имеет смысл делать их "двойного назначения" - одновременно принимающими callback и возвращающими Promise. Это не трудно, но способы написания таких функций выходят за рамки ответа.

Комментариев нет:

Отправить комментарий