Подтвердить что ты не робот

JavaScript ES6 обещает цикл

for (let i = 0; i < 10; i++) {
    const promise = new Promise((resolve, reject) => {
        const timeout = Math.random() * 1000;
        setTimeout(() => {
            console.log(i);
        }, timeout);
    });

    // TODO: Chain this promise to the previous one (maybe without having it running?)
}

Вышеприведенный случай дает следующий случайный вывод:

6
9
4
8
5
1
7
2
3
0

Задача проста: убедитесь, что каждое обещание выполняется только после другого (.then()).

По какой-то причине я не смог найти способ сделать это.

Я попробовал функции генератора (yield), попробовал простые функции, которые возвращают обещание, но в конце дня всегда сводится к одной и той же проблеме: Цикл является синхронным.

С async Я бы просто использовал async.series().

Как вы его решаете?

4b9b3361

Ответ 1

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

Во-вторых, каждое обещание, которое создается с new Promise должно быть разрешено с помощью вызова resolve (или reject). Это должно быть сделано, когда таймер истекает. Это вызовет любого then обратный вызов вы бы на этом обещании. И такой then обратный вызов (или await) является необходимостью для реализации цепи.

С этими ингредиентами есть несколько способов выполнить эту асинхронную цепочку:

  1. С циклом for который начинается с немедленного разрешения обещания

  2. С Array#reduce который начинается с немедленного разрешения обещания

  3. С функцией, которая передает себя как обратный вызов разрешения

  4. С ECMAScript2017 async/await синтаксис

  5. С предложенным ECMAScript2020 for await...of синтаксиса

Смотрите фрагмент и комментарии для каждого из этих вариантов ниже.

1. С for

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

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
    p = p.then(_ => new Promise(resolve =>
        setTimeout(function () {
            console.log(i);
            resolve();
        }, Math.random() * 1000)
    ));
}

Ответ 2

Вы можете использовать async/await для этого. Я бы объяснил больше, но там ничего нет. Это обычный регулярный цикл for, но я добавил ключевое слово await до создания вашего обещания

Что мне нравится в этом, ваше обещание может разрешить нормальное значение вместо того, чтобы иметь побочный эффект, такой как ваш код (или другие ответы здесь). Это дает вам силы, как в "Легенде о Зельде": "Ссылка на прошлое", где вы можете воздействовать на вещи как в свете света, так и в Темном мире, т.е. Вы можете легко работать с данными до/после того, как данные Обещанного доступны, прибегать к глубоко вложенным функциям, другим громоздким структурам управления или глупому IIFE.

// where DarkWorld is in the scary, unknown future
// where LightWorld is the world we saved from Ganondorf
LightWorld ... await DarkWorld

Итак, вот что это будет выглядеть...

const someProcedure = async n =>
  {
    for (let i = 0; i < n; i++) {
      const t = Math.random() * 1000
      const x = await new Promise(r => setTimeout(r, t, i))
      console.log (i, x)
    }
    return 'done'
  }

someProcedure(10).then(x => console.log(x)) // => Promise
// 0 0
// 1 1
// 2 2
// 3 3
// 4 4
// 5 5
// 6 6
// 7 7
// 8 8
// 9 9
// done

Ответ 3

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

цикл (элементы, обработчик): Обещание

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

Скопируйте и вставьте готовый код:

// SEE https://stackoverflow.com/a/46295049/286685
const loop = (arr, fn, busy, err, i=0) => {
  const body = (ok,er) => {
    try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
    catch(e) {er(e)}
  }
  const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
  const run  = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
  return busy ? run(busy,err) : new Promise(run)
}

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

Чтобы использовать его, вызовите его с массивом для цикла в качестве первого аргумента и функцией-обработчиком в качестве второго. Не передавайте параметры для третьего, четвертого и пятого аргументов, они используются внутренне.

const loop = (arr, fn, busy, err, i=0) => {
  const body = (ok,er) => {
    try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
    catch(e) {er(e)}
  }
  const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
  const run  = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
  return busy ? run(busy,err) : new Promise(run)
}

const items = ['one', 'two', 'three']

loop(items, item => {
  console.info(item)
})
.then(() => console.info('Done!'))

Ответ 4

Если вы ограничены ES6, лучшим вариантом будет Promise all. Promise.all(array) также возвращает массив обещаний после успешного выполнения всех обещаний в аргументе array. Предположим, если вы хотите обновить много записей о студентах в базе данных, следующий код демонстрирует концепцию Promise.all в таком case-

let promises = [];
students.map((student, index) => {
  student.rollNo = index + 1;
  student.city = 'City Name';
  //Update whatever information on student you want
  promises.push(student.save());
  //where save() is a function used to save data in mongoDB
});
Promise.all(promises).then(() => {
  //All the save queries will be executed when .then is executed
  //You can do further operations here after as all update operations are completed now
});

Карта является просто примером метода для цикла. Вы также можете использовать for цикла forin или forEach. Так что концепция довольно проста, запустите цикл, в котором вы хотите выполнять массовые асинхронные операции. Переместите каждый такой оператор асинхронной операции в массив, объявленный вне области действия этого цикла. После завершения цикла выполните инструкцию Promise all с подготовленным массивом таких запросов/обещаний в качестве аргумента.

Основная концепция заключается в том, что цикл javascript является синхронным, тогда как вызов базы данных является асинхронным, и мы используем метод push в цикле, который также является синхронизированным. Таким образом, проблема асинхронного поведения не возникает внутри цикла.

Ответ 5

здесь мои 2 цента стоит:

  • resuable function forpromise()
  • эмулирует классический цикл
  • позволяет выполнить ранний выход на основе внутренней логики, возвращая значение
  • может собирать массив результатов, переданных в разрешение /next/collect
  • defaults to start = 0, increment = 1
  • Исключения, заброшенные внутри цикла, пойманы и переданы в .catch()

    function forpromise(lo, hi, st, res, fn) {
        if (typeof res === 'function') {
            fn = res;
            res = undefined;
        }
        if (typeof hi === 'function') {
            fn = hi;
            hi = lo;
            lo = 0;
            st = 1;
        }
        if (typeof st === 'function') {
            fn = st;
            st = 1;
        }
        return new Promise(function(resolve, reject) {

            (function loop(i) {
                if (i >= hi) return resolve(res);
                const promise = new Promise(function(nxt, brk) {
                    try {
                        fn(i, nxt, brk);
                    } catch (ouch) {
                        return reject(ouch);
                    }
                });
                promise.
                catch (function(brkres) {
                    hi = lo - st;
                    resolve(brkres)
                }).then(function(el) {
                    if (res) res.push(el);
                    loop(i + st)
                });
            })(lo);

        });
    }


    //no result returned, just loop from 0 thru 9
    forpromise(0, 10, function(i, next) {
        console.log("iterating:", i);
        next();
    }).then(function() {


        console.log("test result 1", arguments);

        //shortform:no result returned, just loop from 0 thru 4
        forpromise(5, function(i, next) {
            console.log("counting:", i);
            next();
        }).then(function() {

            console.log("test result 2", arguments);



            //collect result array, even numbers only
            forpromise(0, 10, 2, [], function(i, collect) {
                console.log("adding item:", i);
                collect("result-" + i);
            }).then(function() {

                console.log("test result 3", arguments);

                //collect results, even numbers, break loop early with different result
                forpromise(0, 10, 2, [], function(i, collect, break_) {
                    console.log("adding item:", i);
                    if (i === 8) return break_("ending early");
                    collect("result-" + i);
                }).then(function() {

                    console.log("test result 4", arguments);

                    // collect results, but break loop on exception thrown, which we catch
                    forpromise(0, 10, 2, [], function(i, collect, break_) {
                        console.log("adding item:", i);
                        if (i === 4) throw new Error("failure inside loop");
                        collect("result-" + i);
                    }).then(function() {

                        console.log("test result 5", arguments);

                    }).
                    catch (function(err) {

                        console.log("caught in test 5:[Error ", err.message, "]");

                    });

                });

            });


        });



    });