Цикл ожидания против Promise.all

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

let insert = (id,value) => {
    return new Promise(function (resolve, reject) {
        connnection.query(`insert into items (id,value) VALUES (${id},"${value}")`, function (err, result) {
            if (err) return reject(err)
                return resolve(result);
        });
    });
};

Решение Promise.all (ему нужен цикл for для построения массива обещаний ..)

let inserts = [];
for (let i = 0; i < SIZE; i++) inserts.push(insert(i,"..string.."))
Promise.all(inserts).then(values => { 
    console.info("promise all ends");
});

решение цикла ожидания

let inserts = [];
(async function loop() {
    for (let i = 0; i < SIZE; i++) {
        await insert(i, "..string..")
    }
    console.info("await loop ends");
})

Обновлено: спасибо за ответы, но я бы немного углубился в это. await на самом деле не блокирует, мы все это знаем, он блокируется в собственном блоке кода. Цикл await последовательно запускает запросы, поэтому, если в середине 1 запрос занимает больше времени, остальные ждут его. Это похоже на Promise.all: если 1 запрос занимает больше времени, обратный вызов не выполняется, пока ВСЕ ответы не будут возвращены.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
5
0
11 130
3

Ответы 3

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

await loop

for(let i = 0;i < SIZE; i++){
    await promiseCall();
}

Он будет вызывать все обещания параллельно, если какое-либо обещание отклонено, это не повлияет на другие обещания.

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

async function printFiles () {
  const files = await getFilePaths()

  for await (const file of fs.readFile(file, 'utf8')) {
    console.info(contents)
  }
}

Promise.all()

var p1 = Promise.resolve(32);
var p2 = 123;
var p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 100);
});
Promise.all([p1, p2, p3]).then(values => { 
  console.info(values); // [32, 123, "foo"]
});

Это выполнит каждое обещание последовательно и, наконец, вернет объединенный массив оборотных значений.

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

var p1 = Promise.resolve(32);
var p2 = Promise.resolve(123);
var p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("foo");
  }, 100);
});
Promise.all([p1, p2, p3]).then(values => { 
  console.info(values); // 123
});

Основное различие между двумя подходами заключается в том, что

  1. Версия await выдает запросы к серверу последовательно в цикле. Если одна из них выдает ошибку, которую не перехватят, запросы больше не выдаются. Если ошибки запроса перехватываются с помощью блоков try / catch, вы можете определить, какой из запросов завершился ошибкой, и, возможно, запрограммировать некоторую форму восстановления или даже повторить операцию.

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

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

Что вы имеете в виду под фразой «Если какие-либо запросы были успешными и вернули данные, вы потеряете возвращенные данные»? Вы имеете в виду запросы, которые были успешными после, первый неудачный?

nem035 16.12.2018 07:52

Если обещание любой, предоставленное для 'Promise.all', отклоняется, обещание, возвращаемое вызовом `Promise.all ', отклоняется с указанием причины отклонения первого, сделавшего это. Следовательно, вы можете потерять данные для обещаний, преуспевших как до и, так и после отклонения входного обещания.

traktor 16.12.2018 21:29

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

nem035 16.12.2018 21:31

Ваш пример использования Promise.all сначала создаст все обещания, прежде чем ждать их разрешения. Это означает, что ваши запросы будут запускаться одновременно, а обратный вызов, предоставленный Promise.all(...).then(thisCallback), будет срабатывать только в том случае, если все запросы были успешными.

Примечание: обещание, возвращенное из Promise.all, будет отклонено, как только одно из обещаний в данном массиве отклонится.

const SIZE = 5;
const insert = i => new Promise(resolve => {
  console.info(`started inserting ${i}`);
  setTimeout(() => {
    console.info(`inserted ${i}`);
    resolve();
  }, 300);
});

// your code
let inserts = [];
for (let i = 0; i < SIZE; i++) inserts.push(insert(i, "..string.."))
Promise.all(inserts).then(values => {
  console.info("promise all ends");
});

// requests are made concurrently

// output
// started inserting 0
// started inserting 1
// started inserting 2
// ...
// started inserting 4
// inserted 0
// inserted 1
// ...
// promise all ends

Примечание. В этом сценарии было бы проще использовать .map вместо цикла:

Promise.all(
  Array.from(Array(SIZE)).map((_, i) => insert(i,"..string.."))
).then(values => { 
    console.info("promise all ends");
});

С другой стороны, ваш пример использования await ожидает выполнения каждого обещания, прежде чем продолжить и запустить следующее:

const SIZE = 5;
const insert = i => new Promise(resolve => {
  console.info(`started inserting ${i}`);
  setTimeout(() => {
    console.info(`inserted ${i}`);
    resolve();
  }, 300);
});

let inserts = [];
(async function loop() {
  for (let i = 0; i < SIZE; i++) {
    await insert(i, "..string..")
  }
  console.info("await loop ends");
})()

// no request is made until the previous one is finished

// output
// started inserting 0
// inserted 0
// started inserting 1
// ...
// started inserting 4
// inserted 4
// await loop ends

Последствия для производительности в вышеупомянутых случаях напрямую связаны с их различным поведением.

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

С точки зрения сложности, временная сложность для вашего первого примера равна O(longestRequestTime), потому что запросы будут выполняться по существу параллельно, и, таким образом, запрос, занимающий наибольшее время, будет приводить к наихудшему сценарию.

С другой стороны, в примере await есть O(sumOfAllRequestTimes), потому что независимо от того, сколько времени занимают отдельные запросы, каждый из них должен ждать завершения предыдущего, и, таким образом, общее время всегда будет включать в себя все из них.

Проще говоря, игнорируя все другие возможные задержки из-за среды и приложения, в котором выполняется код, для 1000 запросов, каждый из которых занимает 1 с, пример Promise.all все равно займет ~ 1 с, а пример await - ~ 1000 с.

Может, картинка поможет:

time comparison chart

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

Спасибо за объяснение. Это подтверждает то, что я знал. Но вы действительно не можете думать, что Promise.all работает одновременно, поэтому я сомневался в каких-либо реальных различиях между этими двумя решениями ... на самом деле я все еще сомневаюсь

alfredopacino 16.12.2018 14:37

пожалуйста, обратитесь к моему редактированию

alfredopacino 16.12.2018 14:53

Рад помочь. Promise.all работает одновременно. Запросы выполняются практически параллельно. Я прочитал вашу правку и не уверен, в чем вы сомневаетесь. Длинный запрос - это максимальное время выполнения Promise.all, в то время как это всего лишь часть времени выполнения цикла await, поэтому цикл await по своей сути медленнее. В обоих примерах все запросы выполняются, просто они выполняются более эффективно, используя пример Promise.all.

nem035 16.12.2018 18:38

Другие вопросы по теме