Есть массив, для каждого элемента которого следует вызвать асинхронную функцию, возвращающую значение через коллбэк.
Однако, требуется вызвать их последовательно, а именно, следующий вызов надо совершать только тогда, когда завершилась обработка предыдущего.
Вот так получается одновременно:
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 }
Надо делать вызов внутри коллбека. Для этого придётся переписать цикл на рекурсию (ну или не совсем рекурсию):
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 }
В последних версиях языка есть механизмы, позволяющие писать более понятный код чем вариант с рекурсией.
Для начала, надо преобразовать функцию так, чтобы она возвращала обещание (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. Это не трудно, но способы написания таких функций выходят за рамки ответа.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Пишу расширение для Chrome и таймеры, в том числе не высокочастотные (более 1 мин) со временем просто перестают срабатывать в background скрипте расширения
Есть расширение для Мозиллы и Хрома Есть 2 файла CSS, назовем их style1css и style2