Я встретил статью о обещаниях в js , где автор показывает фрагмент кода:

// I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
  result.rows.forEach(function (row) {
    db.remove(row.doc);  
  });
}).then(function () {
  // I naively believe all docs have been removed() now!
});

И говорит

В чем проблема с этим кодом? Проблема в том, что первая функция фактически возвращает undefined, а это означает, что вторая функция не ждет вызова db.remove () для всех документов. Фактически, он ничего не ждет и может выполняться после удаления любого количества документов!

Итак, как я понял, если второй обратный вызов function() {} не принимает аргументов, он фактически не ждет окончания обратного вызова function(result). Правильно ли я делаю вывод? Потому что, когда я запускаю следующий фрагмент кода, он дает противоположный результат:

var array = [];
for ( let i = 0; i < 1000; i++ )
    array.push(i);

var promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve(), 1000);
});

promise
.then(() => array.forEach(item => console.log(item)))
.then(() => console.log("invoked earlier"));

Я жду, пока в середине напечатанных чисел не появится слово «активировано ранее». Но неважно, насколько велико количество предметов. «Вызвано ранее» всегда появляется в конце. Может кто-нибудь объяснить мне, что мне не хватает? Может, статья устарела и с тех пор что-то изменилось?

1
Turkhan Badalov 8 Июл 2017 в 15:05
1
Что я вижу (и если я не ошибаюсь) ... вы упустили одну вещь: в коде, который опубликовал парень, у вас есть forEach, а внутри у вас есть асинхронный вызов ... db.remove() по этой причине он сказал, что код then не будет ждать ... в вашем коде вы просто регистрируете сообщения ... что синхронно ... так что ваш then ждет как и ожидалось
 – 
Elmer Dantas
8 Июл 2017 в 15:20

1 ответ

Лучший ответ

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

Все, что вы вернете из обещания, будет передано в следующее обещание.

В вашем случае вы возвращаете undefined.

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

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

Полагаю, db.remove возвращает обещание ...

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

.then(result => Promise.all(result.rows.map(row => db.remove(row.doc))))
.then((listOfRemoves) => console.log(`removed`));

Принимает ли вторая функция 1 аргумент или 0 аргументов, на 100% несущественно относительно того, когда запускается вторая функция.

Редактировать

Примеры:

.then(() => setTimeout(() => console.log('2nd', 20000)))
.then(() => console.log('first'));

Это происходит потому, что первый тогда не имеет ни малейшего представления о том, что происходит setTimeout. Это не программа для чтения мыслей, она просто запускает код и передает возвращаемое значение (в данном случае undefined).

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

.then(() => new Promise(resolve => setTimeout(resolve, 20000)))
.then(() => console.log('I waited 20 seconds.'));

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

Причина, по которой ваши тесты терпят неудачу, в том, что вы в основном это делаете.

.then(() => console.log('first'))
.then(() => console.log('second'));

Они гарантированно сработают в таком порядке. Период. Все остальные тоже стреляли в таком же порядке. Просто они также планируют асинхронные процессы, как и все другие обратные вызовы / тайм-ауты, которые используют асинхронный ввод-вывод в JS.

5
Norguard 8 Июл 2017 в 15:31
Даже если первая функция .then() не возвращает другое обещание, вторая функция .then() все равно будет вызываться после первой, верно? Я немного запутался в первом предложении.
 – 
PeterMader
8 Июл 2017 в 15:18
Да. Гарантированно случится после. Неважно, сколько аргументов принимает вторая функция. Здесь нет никакой магии, только некоторые вещи в реализации обещания, о которых вы еще не догадывались. В вашем тесте весь ваш собственный код синхронен. Если бы вы поместили setTimeout в первую функцию, вы бы получили ожидаемый результат второго результата, полученного первым ... ... если вы не вернули обещание, которое было выполнено после завершения setTimeout, тогда оно будет вернуться к последовательному.
 – 
Norguard
8 Июл 2017 в 15:19
Таким образом, вам не нужно возвращать обещания из предыдущего обещания , чтобы гарантировать это (как вы говорите в первом предложении).
 – 
PeterMader
8 Июл 2017 в 15:21
Пример с использованием db.remove является асинхронным. Как setTimeout или вызов AJAX. В вашем тесте используется синхронный код. Обратные вызовы Promise не позволяют угадать, есть ли внутри асинхронной функции код, которого нужно дождаться. Таким образом, как и при обычных обратных вызовах, код перейдет к следующему этапу, ожидая, пока асинхронный код будет запланирован и запущен. Вот почему поведение расходится.
 – 
Norguard
8 Июл 2017 в 15:24
1
Верно. Я думаю, что код, который у вас есть, вероятно, немного сложнее, чем нужно, чтобы продемонстрировать это, но да. forEach ничего не возвращает, и все ваши вызовы print являются асинхронными, поэтому все они задерживаются. Из-за комбинации этих двух вещей кажется, что обещания идут в обратном направлении. Нет, вы просто не ждете. Если вы хотите вернуть их в порядок, вы должны использовать return Promise.all(rows.map(...)), а затем он будет ждать выполнения каждого из этих вызовов print, прежде чем переходить к следующему.
 – 
Norguard
8 Июл 2017 в 15:41