异步resolve()是否需要包装?

时间:2019-07-14 03:27:44

标签: javascript node.js

为什么我需要在节点10.16.0中用无意义的异步函数包装resolve()而不在chrome中包装?这是node.js错误吗?

let shoot = async () => console.log('there shouldn\'t be race condition');

(async () => {
  let c = 3;
  while(c--) {

    // Works also in node 10.16.0
    // console.log(await new Promise(resolve => shoot = async (...args) => resolve(...args)));

    // Works is chrome, but not in node 10.16.0?
    console.log(await new Promise(resolve => shoot = resolve));

  };
})();

(async () => {
  await shoot(1);
  await shoot(2);
  await shoot(3);
})();

3 个答案:

答案 0 :(得分:1)

resolve()不同步

调用resolve()(通过shoot())并不会立即触发相关的await(在循环中),而是将事件排队。添加async / await使事件循环有机会唤醒并消耗队列。仅在Chrome await中就足够了,在节点await中需要与实际的async函数耦合。这种对任务的同步不太可靠,并且有可能两次调用相同的resolve()

这是不使用javascript的示例。

答案 1 :(得分:0)

您的代码所依赖的时序问题有些晦涩难懂,涉及到await常数(非承诺)。在您测试的两个环境中,该计时问题显然表现出不同。

每次等待shoot(n)实际上只是在做await resolve(n),而这并不是在等待承诺。由于undefined没有返回值,因此正在等待resolve()

因此,您显然看到了事件循环中的实现差异,并在等待不承诺时承诺实现。您显然希望await resolve()异步,并允许您的while()循环在运行下一个await shoot(n)之前运行,但是我不知道这样做的语言要求,即使有一个实现细节,您可能不应该编写依赖的代码。

我认为基本上是糟糕的代码设计,它依赖于大约同时排队的两个作业的调度的微细节。用强制正确排序的方式而不是依赖调度程序实现的微观细节来编写代码总是更安全的-即使这些细节在规范中,当然也可以不在。

node.js可能是更优化的或有问题的(我不知道是哪个),当对常量执行await时不返回事件循环。或者,如果确实返回到事件循环,它将以优先方式进行,以保持当前代码链的执行,而不是让其他promise继续执行。无论如何,要使此代码正常工作,它必须依靠某些await someConstant行为,而这些行为在各处都不尽相同。

包装resolve()会迫使解释器在每个await shoot(n)之后返回事件循环,因为实际上它正在等待一个诺言,这将使while()循环有机会运行并填充{在调用下一个shoot之前具有新值的{1}}。

答案 2 :(得分:0)

在实现诺言时,这是Node 10(可能)的错误或(可能)过时的行为*。根据{{​​3}},在

await shoot(1);
  1. shoot(1)兑现了new Promise()创建的承诺,该承诺为承诺中的每个反应完成了工作
  2. await undefinedshoot(1)返回的结果)使作业在此语句后继续执行,因为undefined被转换为已兑现的承诺

the ECMAScript spec at the time of this writing中添加了诺言中第一项IIFE中与await相对应的响应,它不涉及任何其他工作;它会立即继续在该IIFE内部。

简而言之,下一个shoot = resolve应该始终在await shoot(n)之后继续执行之前运行。 Node 12 /当前Chrome结果正确。

通常,无论如何您都不会遇到这种类型的错误:正如我在评论中提到的那样,依靠操作来创建特定数量的作业/使用特定数量的Microticks进行同步是不好的设计。如果您想要一种流,其中每个shoot()调用总是会产生循环迭代(即使没有误导的await),这样的方法也会更好:

let available;

(async () => {
  let queue = new Queue();

  while (true) {
    await new Promise(resolve => {
      available = value => {
        resolve();
        queue.enqueue(value);
      };
    });

    available = null;  // just an assertion, pretty much

    while (!queue.isEmpty()) {
      let value = queue.dequeue();

      // process value
    }
  }
})();

shoot(1);
shoot(2);
shoot(3);

具有适当的队列实现。 (然后,您可以考虑使用异步迭代器来使队列的使用变得整洁。)

*不确定此处的确切历史记录。可以肯定的是,ES规范曾经用于引用微任务,但现在它们正在工作。当前稳定的Firefox匹配节点10。await所花费的时间可能比以前少。这种事情是产生以下建议的原因。