【问题标题】:Why do promises execute at the point of declaration?为什么 Promise 会在声明时执行?
【发布时间】:2018-09-16 01:38:01
【问题描述】:

我想使用 Promise.all() 执行一组承诺。我的方法是将这些承诺放在一个数组中,然后将数组传递给 Promise.all()。但是,我发现 Promise 在声明后立即开始执行,甚至不等待 Promise.all 被调用。

为什么会发生这种情况?如何让 Promise 仅在调用 Promise.all() 时执行?

let promiseArray = [];
const values = [1, 2, 3, 4, 5];

values.forEach((value)=>{
  promiseArray.push(
    new Promise((resolve, reject)=>{
      console.log('value: ' + value);
      return resolve();
    })
  )
})


console.log('start')
Promise.all(promiseArray)

/*
 output is 

  value: 1
  value: 2
  value: 3
  value: 4
  value: 5
  start

 would have expected output to have been

  start
  value: 1
  value: 2
  value: 3
  value: 4
  value: 5
*/

【问题讨论】:

  • 这就是它们的设计工作方式——Promise 执行器会立即同步执行......为什么没有简单的答案。
  • 如果您希望代码按预期运行,只需更改为一行...promiseArray.push(() => 然后Promise.all(promiseArray.map(fn => fn()))
  • 你的直觉其实是有道理的。急切执行 Promises 构造函数的设计决定至少是有问题的。

标签: javascript node.js promise es6-promise


【解决方案1】:

将 Promise 视为“执行”会让您感到困惑。 Promise 纯粹是一种通知机制。它通常与一些底层异步操作相关联,当您创建 Promise 时,通常会启动异步操作。

Promise.all() 然后用于跟踪您已经开始的一大堆异步操作何时完成(或以错误结束)。

所以,你不要使用Promise.all() 来开始一堆事情。您只使用它来跟踪它们何时全部完成以及它们何时在您的代码中的其他地方启动。


当您使用new Promise() 手动创建承诺时,承诺执行器会立即执行。它们就是这样设计的。

如果您在 Promise 执行器中有真正的异步操作,并且当这些操作完成时您正在执行 console.log(),您可能不会发现 Promise 的设计方式有任何问题。我认为您的大部分困惑源于您的承诺执行程序中实际上没有任何异步操作,因此实际上没有理由为此使用承诺。 Promise 旨在跟踪异步操作的完成情况。如果您没有实际的异步操作,则没有理由使用它们。

仅供参考,如果您想在未来某个时间从 Promise 执行器内部启动一些异步操作,您可以使用正常的 setTimeout()process.nextTick()setImmediate() 操作来安排操作稍后启动。

本来预期的输出是

另外,您似乎希望您的输出具有严格的顺序。 Promise.all() 期望有 N 个异步操作并行运行,并且这些 N 个操作没有保证的完成顺序。相反,Promise.all() 将跟踪它们,收集所有结果并按顺序向.then() 处理程序显示结果数组(如果它们都成功解决)。它本身不会按顺序运行操作。这些操作并行运行,并以它们采用的任何自然顺序完成。

【讨论】:

  • 这让我大吃一惊!我对 Promise 的心理模型一直是它们是“解决”而不是“返回”的“特殊”函数,因此它们适用于异步操作。现在我只需要弄清楚为什么我当前的代码可以工作¯_(ツ)_/¯
  • @takinola - 这不是一个罕见的混淆。但是,如果你有一个返回 Promise 的函数,可以这样想。该函数启动一个异步操作,然后创建一个新的 Promise 并将其挂钩以监视该异步操作。然后该函数返回该承诺,它就像一个跟踪器,您可以使用它来了解异步操作何时完成以及它完成了什么结果。 Promise 不是操作本身,只是一种跟踪机制。一个通常只有一个函数来启动异步操作并得到一个承诺来跟踪它。
【解决方案2】:

是的,promise 会立即执行。他们不能以任何其他方式工作。 Promise 就像 JS 中的任何其他对象一样,完全不知道引用它们的对象以及它们是如何传递的,因此它们无法“等待”直到完成对它们的引用的特定操作。

在您的情况下,解决方案是在将 Promise 传递给 Promise.all() 时创建它们:

let promiseFnArray = [];
const values = [1, 2, 3, 4, 5];

values.forEach((value) => {
  promiseFnArray.push((resolve, reject) => {
    console.log('value: ' + value);
    return resolve();
  }))
})

console.log('start')
Promise.all(promiseFnArray.map(promiseFn => new Promise(promiseFn));

另外,一旦你的 promise 真正实现了异步,它们不一定会按照原始顺序解决。要以正确的顺序获得结果,请使用 Promise.all( ... ).then(),这是 Promise.all() 的全部目的

【讨论】:

  • [...]They couldn't work any other way. [...] 这不是真的new Promise 可能会延迟使用setImmediate 传递给构造函数的回调的执行,然后start 将出现在value: 输出之前。但这并没有多大意义,因为 Promise 通常用于观察异步代码(参见jfriend00 的答案)
  • @t.niese AFAIK, setImmediate 不是标准的,它不会延迟 Promise 对执行程序的调用,它只是意味着您将代码包装在一个单独的延迟中。这与 Promise 本身或它如何传递无关。 "couldn't work any other way" 是指 Promise 将能够根据引用它的变量所发生的情况来调用执行程序的概念(在这种情况下被传递给 @ 987654332@)
  • 是的 setImmediate/setTimeout ... 不是语言规范的一部分,我只是将其用作说明。 OP想知道为什么start会显示在value之后。经常会出现这种假设,因为 Promises 用于异步代码,并且在 nodejs 中,所有可能称为异步的回调都应始终称为异步(所有那些 err, value 回调)。所以有些人期望传递给new Promise 的回调也将被称为异步,就像那些err, value 回调一样。而 Promise 实现肯定可以做到这一点,但它没有意义。
猜你喜欢
  • 2012-01-06
  • 1970-01-01
  • 2017-02-20
  • 1970-01-01
  • 2019-08-24
  • 1970-01-01
  • 2022-12-28
相关资源
最近更新 更多