承诺不会以我期望的方式解决

时间:2018-01-18 06:56:49

标签: javascript promise

我有以下内容:

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()电话中的所有日志。我期待看到他们交替。我理解,根据this文章,承诺不会按顺序解决,但我不会期望我的承诺解决,直到我致电resolve()所以仍然期望交替日志,即使不按顺序。

为什么不是这样?

2 个答案:

答案 0 :(得分:3)

通过避免在新的手动创建的承诺中包含承诺的promise anti-pattern,您的代码可以编写得更清晰。相反,您只需将外部promise添加到数组中,并通过从.then()处理程序内部返回将内部promises链接到外部promise。它可以像这样简单地完成:

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()调用完成之间的相对时间只是一场比赛,具体取决于他们何时开始(他们前面的.attachRef()花了多长时间)以及他们自己的.saveDoc()多长时间呼叫执行。所有这些的相对时间可能不完全可预测,特别是如果在幕后有一个可以同时处理多个请求的多线程数据库。

相对时间不可预测的事实不应该是一个惊喜。您有意并行地运行多个两阶段异步操作,这意味着您不关心它们运行或完成的顺序。它们都在相互竞争。如果它们都执行完全相同的时间而没有变化,那么它们可能按照它们启动的顺序完成,但执行时间的任何微小变化都可能会改变完成顺序。如果底层数据库同时在飞行中的所有不同请求之间存在锁争用,那么这也会大大改变时间。

因此,此代码旨在并行执行操作,并在完成后通知您。根据定义,这意味着您并不关心控制事情的确切顺序,只有当它们全部完成时才会完成。

答案 1 :(得分: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日志

的原因