“我该怎么做?”;“收益率”;用JavaScript?

“我该怎么做?”;“收益率”;用JavaScript?,javascript,promise,async-await,Javascript,Promise,Async Await,我对现代JavaScript(ES8)有点陌生。异步生成的首选方法是什么,即使用wait,在事件循环的某些未来迭代中继续执行脚本?我看到了以下选项: async function yield1() { await Promise.resolve(); console.log("done"); } async function yield2() { // setImmediate is non-standard, only Edge and Node have it await

我对现代JavaScript(ES8)有点陌生。异步生成的首选方法是什么,即使用
wait
,在事件循环的某些未来迭代中继续执行脚本?我看到了以下选项:

async function yield1() {
  await Promise.resolve();
  console.log("done");
}

async function yield2() {
  // setImmediate is non-standard, only Edge and Node have it
  await new Promise(done => (setImmediate? setImmediate: setTimeout)(done));
  console.log("done");
}

async function yield3() {
  await new Promise(done => setTimeout(done));
  console.log("done");
}
我应该一个接一个地选择还是都一样?或者它取决于环境(节点、浏览器)


更新,在评论中被问到我想要实现什么。它是一个简单的可观察对象,当其属性更改时,它会异步触发
propertyChanged
事件。这是一个完整的示例,“屈服”部分位于
firePropertyChanged
内:

const EventEmitter = require('events');

class Model extends EventEmitter {
  constructor(data) {
    super();
    this._data = data;
  }

  get data() {
    return this._data;
  }

  set data(newValue) {
    const oldValue = this._data;
    if (oldValue !== newValue) {
      this._data = newValue;
      this.firePropertyChanged('data', newValue, oldValue);
    }
  }

  async firePropertyChanged(property, newValue, oldValue) {
    await Promise.resolve().then(() =>
      super.emit('propertyChanged', { target: this, property, newValue, oldValue }));
    console.log('all propertyChanged handlers have been called asynchronously');
  }
}

async function waitForChange(obj) {
  await new Promise(resolve => 
    obj.once('propertyChanged', args => 
      console.log(`propertyChanged: ${args.property}, ${args.oldValue} -> ${args.newValue}`)));
}

async function test() {
  const obj = new Model("old");
  var change = waitForChange(obj);
  console.log(`before change: ${obj.data}`);
  obj.data = "new";
  console.log(`after change: ${obj.data}`);
  await change;
}

test().catch(e => console.error(e));
如果使用节点运行,则预期输出应为:

before change: old after change: new propertyChanged: data, old -> new all propertyChanged handlers have been called asynchronously 变革前:旧 更改后:新 属性更改:数据,旧->新 已异步调用所有propertyChanged处理程序
此输出的顺序很重要,也就是说,我不希望在
数据的setter方法返回给调用方之前调用
属性更改的任何事件处理程序。

好的,我将在您的评论中介绍您的问题的新摘要(您可能应该编辑您的问题,这样说):

我希望以最有效的方式在事件循环的未来迭代中运行一段代码(并让当前方法返回)。没有特别的偏好,但是延续的顺序应该很重要。例如,在我的示例中,如果property1发生了变化,那么property2也发生了变化,我首先希望property1触发propertyChanged,然后是property2触发propertyChanged(在这两种情况下,与更改两个属性的代码异步)

简短的版本是,您可以使用下面的任何选项来解决您的问题。在不了解您的具体情况/需求的情况下,我可能会建议您使用
setImmediate()
,因为如果递归触发,它不会使事件队列饥饿,但是
process.nextTick()
Promise.resolve()。然后()
将更快地触发(在其他类型的事件之前)如果这对你的来电者很重要

以下是对每种选择的一些解释——每种选择都可能实现您的目标,但在某些细节上有所不同

所有这些选项都允许事件循环的当前勾号完成,然后它们安排在事件循环的未来勾号上调用回调。它们在调用下一个回调的确切时间上有所不同,有些则在根据当前处理的事件类型(例如,事件循环在扫描多个不同事件队列的过程中的位置)安排下一个回调时有所不同

您可以从阅读这篇概述文章开始

处理下一个时钟(cb)

这是安排回调的最快方法。事件循环的当前勾号完成其执行,然后在node.js事件循环代码查看事件循环中的任何其他事件队列之前,它在
nextTickQueue
中查找并运行它们。注意,如果持续递归调用
process.nextTick()
,则可能会“饿死”事件循环,因为在
nextTickQueue
为空之前,它不会给其他事件运行的机会。这不是一个“公平”的调度程序

setImmediate(cb)

这将安排在事件循环的当前“阶段”完成后运行回调。您可以将事件循环视为在许多不同类型的队列中循环。当当前正在处理的队列类型为空时,将处理任何挂起的
setImmediate()
回调

注意,这与其他类型的事件的关系取决于调用
setImmediate()
时处理的事件类型

例如,如果您在从
fs.read()
的完成回调中,并调用
setImmediate()
来安排回调,那么事件循环将首先处理任何其他挂起的I/O事件,然后再处理
setImmediate()
回调。因为只有在事件循环前进到事件队列中的下一种类型的事件时才会调用它,所以不能使用
setImmediate()
使事件循环饥饿。即使递归调用
setImmediate()
也会循环所有事件

相对于计划的
setImmediate()
而言,如何处理挂起的
setTimeout()
取决于调用
setImmediate()
时处于事件循环的哪个阶段。这通常超出了代码中应该注意的范围。如果像这样的多个异步操作的相对定时很重要,那么您只需编写保证给定序列的代码就更安全了,而不管这些操作是在何时通过回调启用的。承诺可以帮你安排这样的事情

设置超时(cb,0)

计时器是事件循环的一个阶段。当它在事件循环中查看不同类型的事件队列时,其中一个阶段是查找时间已过的任何计时器事件,因此是时候调用它们的回调了。由于计时器仅在事件循环处于“计时器阶段”时运行,因此它们相对于其他类型的事件的触发方式是不确定的。它取决于计时器准备就绪时事件循环在其循环中的位置。就个人而言,我通常不使用
setTimeout(cb,0)
,除非我尝试与其他计时器事件同步,因为这将保证FIFO顺序与其他计时器事件同步,但不会与其他类型的事件同步

Promise.resolve()。然后(cb)

要获得承诺的这种详细程度(通常不需要),您必须非常清楚您正在使用的承诺实现是什么以及它是如何工作的。非本机代码承诺实现将使用另一种计时机制