#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. Это не трудно, но способы написания таких функций выходят за рамки ответа.
Комментариев нет:
Отправить комментарий