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

Javascript 承诺不会以我期望的方式解决,javascript,promise,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 => { c

我有以下资料:

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')
})
此._sequenceService.attachRef有一个console.log调用

运行此命令时,我将看到this.\u sequenceService.attachRef调用中的所有控制台日志,然后我将看到saveDoc.then调用中的所有日志。我希望看到他们交替出现。我理解,根据文章,承诺不会按顺序解决,但在我调用resolve之前,我不会期望我的承诺会解决,所以即使不按顺序,也会期望交替日志


为什么不是这样?

澄清我的上述评论:

在这种情况下,承诺分为四个层次

级别0:使用Promise.all创建承诺

级别1:为每项工作创建新承诺

级别2:由此生成的承诺。\u sequenceService.attachRefjob

级别3:由此生成的承诺。_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

这可能就是为什么您会看到所有二级日志,然后是所有三级日志,最后是承诺。所有日志澄清了我的上述评论:

在这种情况下,承诺分为四个层次

级别0:使用Promise.all创建承诺

级别1:为每项工作创建新承诺

级别2:由此生成的承诺。\u sequenceService.attachRefjob

级别3:由此生成的承诺。_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

这可能就是为什么您会看到所有的二级日志,然后是所有的三级日志,最后是Promise.all log

通过避免将Promise包装到新的手动创建的Promise中,您的代码可以写得更干净。相反,您只需将外部承诺推送到数组中,并通过从.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的所有调用都是异步的。这意味着调用它们只是启动操作,然后它们返回,剩下的代码继续运行。这也称为非阻塞。 那么处理程序是异步的。他们最早能跑的时间是下一次。 因此,这解释了为什么发生的第一件事是执行所有对.attachRef的调用,因为循环就是这样做的。它立即调用所有的.attachRef方法。由于它们刚刚开始操作,然后立即返回,for循环很快就完成了启动所有.attachRef操作的工作。 然后,当每个.attachRef完成时,它将触发调用相应的.saveDoc。 .saveDoc调用finish之间的相对时间只是一场竞赛,这取决于它们开始的时间、它们之前的.attachRef花费的时间以及它们自己的.saveDoc调用执行的时间。所有这些的相对时间可能不完全可预测,特别是如果幕后有一个多线程数据库可以同时处理多个请求。 相对时间是不可预测的,这一事实并不令人惊讶。您正在有目的地并行运行多个两阶段异步操作,这意味着您不在乎它们以什么顺序运行或完成。他们都在比赛。如果它们执行的时间完全相同,没有变化,那么它们可能会按照启动的顺序完成,但执行时间的任何微小变化都肯定会改变完成顺序。如果底层DB GET在同时进行的所有不同请求之间存在锁争用,这也会极大地改变时间


因此,这段代码设计成并行处理,并在所有操作完成时通知您。从某种意义上说,这意味着您不关心控制事情运行或完成的精确顺序,只有在它们全部完成时才考虑。通过避免将承诺包装到新的手动创建的承诺中,您的代码可以编写得更干净。相反,您只需将外部承诺推送到数组中,并通过从.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的所有调用都是异步的。这意味着调用它们只是启动操作,然后它们返回,剩下的代码继续运行。这也称为非阻塞。 那么处理程序是异步的。尽可能早地 下一个滴答声是跑步。 因此,这解释了为什么发生的第一件事是执行所有对.attachRef的调用,因为循环就是这样做的。它立即调用所有的.attachRef方法。由于它们刚刚开始操作,然后立即返回,for循环很快就完成了启动所有.attachRef操作的工作。 然后,当每个.attachRef完成时,它将触发调用相应的.saveDoc。 .saveDoc调用finish之间的相对时间只是一场竞赛,这取决于它们开始的时间、它们之前的.attachRef花费的时间以及它们自己的.saveDoc调用执行的时间。所有这些的相对时间可能不完全可预测,特别是如果幕后有一个多线程数据库可以同时处理多个请求。 相对时间是不可预测的,这一事实并不令人惊讶。您正在有目的地并行运行多个两阶段异步操作,这意味着您不在乎它们以什么顺序运行或完成。他们都在比赛。如果它们执行的时间完全相同,没有变化,那么它们可能会按照启动的顺序完成,但执行时间的任何微小变化都肯定会改变完成顺序。如果底层DB GET在同时进行的所有不同请求之间存在锁争用,这也会极大地改变时间



因此,这段代码设计成并行处理,并在所有操作完成时通知您。从某种意义上说,这意味着你不关心控制事物运行或完成的精确顺序,只有当它们全部完成时。

您可以直接将then函数返回数组,而无需将其包装在promiseI中。最初我这样做了,但将其更改为此,以便我可以调试为什么控制台日志的顺序不符合我的预期。我想尝试手动解析可能会有所帮助。现在它解析正确了吗?日志将不会交替。。Promise.all并行激发所有承诺,在每种情况下,第一个请求都会执行此操作。_sequenceService.attachRef将为所有承诺执行。它们可能都在第二个请求之前完成completed@ChiragRavindra-我明白,但不保证。所有人都对待我的新承诺者自己,拒绝作为解析的承诺,并等待我调用resolve以确定其已解析?您可以直接将then函数返回到数组,而无需将其包装在promise中。我最初这样做了,但将其更改为此,以便我可以调试控制台日志为何不符合我预期的顺序。我想尝试手动解析可能会有所帮助。现在它解析正确了吗?日志将不会交替。。Promise.all并行激发所有承诺,在每种情况下,第一个请求都会执行此操作。_sequenceService.attachRef将为所有承诺执行。它们可能都在第二个请求之前完成completed@ChiragRavindra-我理解,但不承诺。所有人都把我的新承诺人视为自己,拒绝作为解决承诺,等待我打电话解决,以确定问题是否解决?好的,现在很清楚了,谢谢。我想你没有办法让它按照我期望的方式工作,是吗?我有时会使用Promise Factorys函数数组,这些函数返回承诺,并在数组上执行reduce调用,以构建顺序承诺链,而不是Promise。所有这些都是在订单很重要的情况下进行的。。Ex:promisefactoryArray.reducechain,pf=>chain.then=>pf,Promise.Reolve然而,我觉得这里详述的解决方案超出了这个问题的框架。。您还可以查看async/wait库来强制顺序执行。这很好,谢谢。我同意这超出了范围。在这个例子中,我可能会等待它,现在我对引擎盖下发生的事情有了更好的理解。对不起,我已经将另一个答案标记为对这个答案的正确回答,因为它会更详细地说明发生了什么以及为什么。我非常感谢你花时间和精力帮助我理解,不用担心!无论什么答案最好:好的,好的,现在很清楚了,谢谢。我想你没有办法让它按照我期望的方式工作,是吗?我有时会使用Promise Factorys函数数组,这些函数返回承诺,并在数组上执行reduce调用,以构建顺序承诺链,而不是Promise。所有这些都是在订单很重要的情况下进行的。。Ex:promisefactoryArray.reducechain,pf=>chain.then=>pf,Promise.Reolve然而,我觉得这里详述的解决方案超出了这个问题的框架。。您还可以查看async/wait库来强制顺序执行。这很好,谢谢。我同意这超出了范围。我可能会在这个例子中等待它,现在我更了解引擎盖下发生的事情了。对不起,我已经将另一个答案标记为正确的
因为它会更详细地描述发生了什么以及为什么会发生。我非常感谢你花时间和精力帮助我理解,不用担心!无论什么答案最好:精彩的解释,非常感谢。需要注意的是,我只在调试时使用反模式,因为我想确保它不仅仅是我以前所使用的方式,这也是您所描述的应该使用的方式。我真的想让它像这样工作,只是让我知道什么时候所有的事情都完成了,但我很好奇为什么订购不是我预期的。我现在明白了。精彩的解释,非常感谢。需要注意的是,我只在调试时使用反模式,因为我想确保它不仅仅是我以前所使用的方式,这也是您所描述的应该使用的方式。我真的想让它像这样工作,只是让我知道什么时候所有的事情都完成了,但我很好奇为什么订购不是我预期的。我现在明白了。