У меня есть следующее:
for (let job of jobs) {
resets.push(
new Promise((resolve, reject) => {
let oldRef = job.ref
this._sequenceService.attachRef(job).then(() => {
this._dbService.saveDoc('job', job).then(jobRes => {
console.log('[%s / %s]: %s', oldRef, jobRes['jobs'][0].ref, this._moment.unix(job.created).format('DD/MM/YYYY HH:mm'))
resolve()
}).catch(error => {
reject(error)
})
}).catch(error => {
reject(error)
})
})
)
}
return Promise.all(resets).then(() => {
console.log('Done')
})
this._sequenceService.attachRef
имеет вызов console.log()
.
Когда это выполняется, я вижу все консольные журналы из this._sequenceService.attachRef()
а затем я вижу все журналы в saveDoc.then()
. Я ожидал увидеть их чередующимися. Я понимаю, что в соответствии с этой статьей обещания не разрешаются по порядку, но я не ожидал, что мое обещание разрешится до тех пор, пока я не назову resolve()
поэтому все равно ожидаю чередующиеся журналы, даже если они не в порядке.
Почему это не так?
Ваш код может быть написан намного чище, избегая обещания анти-шаблона обернуть обещание в новом созданном вручную обещании. Вместо этого вы просто выдвигаете внешнее обещание в свой массив и связываете внутренние обещания с внешним, возвращая их из обработчика .then()
. Все это можно сделать так просто:
for (let job of jobs) {
let oldRef = job.ref;
resets.push(this._sequenceService.attachRef(job).then(() => {
// chain this promise to parent promise by returning it
// inside the .then() handler
return this._dbService.saveDoc('job', job).then(jobRes => {
console.log('[%s / %s]: %s', oldRef, jobRes['jobs'][0].ref, this._moment.unix(job.created).format('DD/MM/YYYY HH:mm'));
});
}));
}
return Promise.all(resets).then(() => {
console.log('Done')
}).catch(err => {
console.log(err);
});
Отклонения автоматически распространяются вверх, поэтому вам не нужны обработчики .catch()
внутри цикла.
Что касается последовательности, вот что происходит:
for
является синхронным. Он сразу же заканчивается..attachRef()
являются асинхронными. Это означает, что вызов их только инициирует операцию, а затем они возвращаются, а остальная часть вашего кода продолжает работать. Это также называется неблокирующим..then()
являются асинхронными. Самое раннее, что они могут запустить, - это следующий тик..attachRef()
выполняемые с тех пор, как это делает цикл. Он сразу же вызывает все методы .attachRef()
. Так как они только начинают свою работу, а затем сразу возвращаются, цикл for
завершает работу, довольно быстро запуская все операции .attachRef()
..attachRef()
, он вызывает .saveDoc()
соответствующий .saveDoc()
..saveDoc()
вызовов .saveDoc()
- это просто гонка, в зависимости от того, когда они начали (сколько времени .attachRef()
их .attachRef()
), и как долго их собственный .saveDoc()
принял для выполнения. Это относительное время для всех этих событий, вероятно, не вполне предсказуемо, особенно если есть многопоточная база данных за кулисами, которая может обрабатывать несколько запросов одновременно.Тот факт, что относительное время не предсказуемо, не должно быть неожиданностью. Вы выполняете несколько двухступенчатых асинхронных операций параллельно, что означает, что вам все равно, какой порядок они запускают или завершают. Они все участвуют друг в друге. Если бы все они выполняли ровно одно и то же время, чтобы выполнить без изменений, то они, вероятно, закончили бы в том же порядке, в котором они были запущены, но любые незначительные изменения времени выполнения могут, безусловно, изменить порядок завершения. Если базовая БД получает, то одновременно имеет место конфликт блокировки между всеми различными запросами в полете, что также может кардинально изменить время.
Таким образом, этот код предназначен для параллельной работы и уведомления, когда все это делается. Несколько по определению это означает, что вы не заботитесь о том, чтобы контролировать точный порядок вещей, которые выполняются или завершаются, только когда все это делается.
Уточнение моих комментариев выше:
В этом случае обещания находятся на четырех уровнях.
Уровень 0: Обещание, созданное с помощью Promise.all
Уровень 1: Обещание, созданное с new Promise
для каждого задания
Уровень 2: обещание, генерируемое this._sequenceService.attachRef(job)
Уровень 3: обещание, сгенерированное this._dbService.saveDoc('job', job)
Пусть говорят, что у нас есть два задания J1 и J2
Один возможный порядок исполнения:
L0 invoked
J1-L1 invoked
J2-L1 invoked
J1-L2 invoked
J2-L2 invoked
J1-L2 resolves, log seen at L2 for J1
J1-L3 invoked
J2-L2 resolves, log seen at L2 for J2
J2-L3 invoked
J1-L3 resolves, log seen at L3 for J1
J1-L1 resolves
J2-L3 resolves, log seen at L3 for J2
J2-L1 resolves
L0 resolves, 'Done' is logged
Вероятно, поэтому вы видите все журналы L2, затем все журналы L3, а затем, наконец, журнал Promise.all
Promise.all
для массива, чтобы построить последовательную цепочку обещаний (вместо Promise.all
), когда порядок важен. Пример: promisefactoryArray.reduce((chain,pf)=>(chain.then(()=>pf())), Promise.reolve())
Однако подробное описание решения, которое я считаю, выходит за рамки способа, которым сформулирован этот вопрос. Вы могли бы также посмотрите на библиотеку async / wait, чтобы вызвать последовательное выполнение.
resolve()
вручную может помочь.