Обещания не решаются так, как я ожидал

1

У меня есть следующее:

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() поэтому все равно ожидаю чередующиеся журналы, даже если они не в порядке.

Почему это не так?

  • 0
    вы можете напрямую вернуть функцию then в массив, не заключая ее в обещание
  • 0
    Первоначально я сделал это, но изменил на это, так что я могу отлаживать, почему журналы консоли не в порядке, который я ожидаю. Я думал, что попытка resolve() вручную может помочь.
Показать ещё 7 комментариев
Теги:
promise

2 ответа

3
Лучший ответ

Ваш код может быть написан намного чище, избегая обещания анти-шаблона обернуть обещание в новом созданном вручную обещании. Вместо этого вы просто выдвигаете внешнее обещание в свой массив и связываете внутренние обещания с внешним, возвращая их из обработчика .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
    Блестящее объяснение, большое спасибо. Просто чтобы заметить, я использовал анти-шаблон только при отладке, так как я хотел убедиться, что это было не просто так, как я делал это раньше (как вы описали, это должно быть сделано). Я действительно хочу, чтобы все работало так, как есть (просто дайте мне знать, когда все будет сделано), но мне было любопытно, почему порядок не был таким, как я ожидал. Теперь я понимаю.
2

Уточнение моих комментариев выше:

В этом случае обещания находятся на четырех уровнях.

Уровень 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

  • 0
    Хорошо, хорошо, теперь все ясно, спасибо. Я не думаю, что у вас есть способ заставить это работать так, как я ожидаю, а вы?
  • 0
    Я иногда использую Массив фабрик Promise (функции, которые возвращают обещания) в сочетании с вызовом Promise.all для массива, чтобы построить последовательную цепочку обещаний (вместо Promise.all ), когда порядок важен. Пример: promisefactoryArray.reduce((chain,pf)=>(chain.then(()=>pf())), Promise.reolve()) Однако подробное описание решения, которое я считаю, выходит за рамки способа, которым сформулирован этот вопрос. Вы могли бы также посмотрите на библиотеку async / wait, чтобы вызвать последовательное выполнение.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню