事件发射器按顺序或并行发射,以及它们异步时的行为



考虑以下代码:

import events from 'events';
const eventEmitter = new events.EventEmitter();
eventEmitter.on('flush', (arg)=>{
let i=0;
while(i<=100000){
console.log(i, arg);
i++;
}
})
setTimeout(()=>{
eventEmitter.emit('flush', `Fourth event`);
}, 5000);
setTimeout(()=>{
eventEmitter.emit('flush', `Third event`);
}, 4000);
setTimeout(()=>{
eventEmitter.emit('flush', `First event`);
}, 2000);
setTimeout(()=>{
eventEmitter.emit('flush', `Second event`);
}, 3000);

输出1:

1 First event
2 First event
3 First event
.
.
.
1 Second event
2 Second event
3 Second event
.
.
.
1 Third event
2 Third event
3 Third event
.
.
.
1 Fourth event
2 Fourth event
3 Fourth event

我想知道的是,发出的事件是否首先完成,然后只发出第二个事件?或者我可以期待这样的东西吗:

输出2:

1 First event
1 Second event
2 Third event
3 Fourth event
3 Third event
.
.
.

如果我这样写emitter.on函数:

eventEmitter.on('flush', (arg)=>{
const mFunc = async ()=>{
let i=0;
while(i<=100000){
console.log(i, arg);
i++;
}
}
mFunc();
})  

我本来期待着像Output2那样的输出,但是,我得到了类似于Output1的东西。有人能给我解释一下这种行为吗?

另外,考虑一下这种情况:

eventEmitter.on('flush', (arg)=>{
const mFunc = async ()=>{
let i=0;
while(i<=100000){
console.log(i, arg);
i++;
await updateValueInDatabase(i);
}
}
mFunc();
}) 

现在,函数的行为是什么?

将我的评论合并为一个答案,并添加更多评论和解释。。。

EventEmitter发出事件是100%同步代码。它不经过事件循环。因此,一旦您调用.emit(),这些事件处理程序就会运行,直到它们完成后,任何其他系统事件(例如您的计时器)才能运行。

从调用的角度来看,调用async函数与调用非异步函数没有任何不同。它不会改变任何事情。现在,在async函数中,您可以使用await,这将导致它挂起执行并立即返回promise,但如果没有await,使函数异步只会使它返回promise,但不会更改其他任何内容。因此,调用mFunc()的async版本与调用它不是async完全相同(只是它返回了一个您不使用的promise)。根本不会改变事件的顺序。

既然你显然认为它会改变事情,我真的建议你多读一些关于async函数是什么的文章,因为你对它的作用的看法显然与它的实际作用不同。所有async函数都返回一个promise并捕获内部异常,这些异常会变成一个被拒绝的promise。如果您不使用await,它们只是像其他一切一样同步运行,然后返回一个promise。

调用async函数时,返回ONLY当您命中await时,或者当函数完成执行并在函数末尾命中return或隐式return时。如果没有await,它只是像一个正常函数一样运行,然后返回一个promise(您没有使用它做任何事情)。正如我上面所说,您确实需要阅读更多关于async函数的内容。你目前的理解显然是错误的。

以下是async函数的一些总结特性:

  1. 他们总是承诺回报
  2. 无论函数最终用return语句返回什么值,该promise都会被解析;如果没有return语句,则用undefined解析
  3. 它们同步运行,直到它们达到return语句或await
  4. 当他们遇到await时,他们立即返回一个promise,并暂停函数的进一步执行,直到await打开的promise得到解决
  5. 当它们挂起并返回promise时,调用者将接收该promise并继续执行。调用方不会挂起,除非调用方也对该承诺执行await
  6. 它们还捕获异步回调中不属于自己的任何同步异常或其他异常,并将这些异常转换为被拒绝的promise,因此async函数返回的promise将被拒绝,并将异常作为原因
  7. 因此,如果async函数中没有await,它们只是同步运行并返回promise

在您的上一个版本的代码中,如果您最终使事件发射器处理程序实际上是异步的,并且实际上await的长度足以让其他事情获得一些周期,那么您可以在等待通知其侦听器的计时器和承诺之间创建一些竞争。等待通知听众的promise将在计时器之前运行。这使得你混合这两种类型的情况非常复杂,并且非常依赖于时间。等待通知听众的承诺的无尾序列可以使计时器等待,直到没有更多的承诺等待通知。但是,如果有一刻没有承诺等待通知,那么你的下一个计时器将启动,并启动它自己的一组承诺驱动的操作,然后所有承诺驱动的运算可能会交错进行。

此外,emitter.emit()没有承诺意识。它不注意监听发射的回调的返回值。因此,听众是否是asyncemitter.emit()来说根本没有任何区别。一旦他们回来(无论他们是否做出承诺),它就会直接进入下一步要做的事情。承诺仅在接收方将其与await.then().catch()一起使用时才影响代码流。在这种情况下,接收方不处理返回值,因此emitter.emit()直接进入其下一个业务订单并执行该订单。

好吧,如果我有一堆异步函数数组[async1,async2,async3,…],并且它们内部都有等待语句,那么按顺序执行它们的最佳方式是什么?即按照索引的顺序一个接一个?

好吧,如果你有一个异步函数数组,当它们实际完成工作时,它们能够正确地解析它们的承诺,那么你可以通过使用await在数组中循环来顺序执行它们。

async function someFunc() {
const jobs = [asyncFn1, asyncFn2, asyncFn3];
for (let job of jobs) {
let result = await job(...);
}
}

在内部,EvenEmitter为每个事件类型保存一个侦听器数组。当您发出一个事件类型时,EvenEmitter只是通过它的侦听器数组并执行它。因此,侦听器是按照addListeneron方法添加的顺序执行的。

为了回答您的问题,有两个部分:a)它按照您添加侦听器的顺序执行,b)它取决于侦听器是否异步。如果您的侦听器是同步的,那么您可以预期这种行为。如果您的侦听器是异步的,您就不能指望会这样。即使同步执行异步,它们也不一定同时解决,因为这是异步的本质。

最新更新