意外行为混合process.nextTick与async / await。事件循环在这里如何工作?

时间:2018-11-04 06:53:05

标签: javascript node.js promise async-await es6-promise

我的代码:

async function run(){

    process.nextTick(()=>{
        console.log(1);
    });

    await (new Promise(resolve=>resolve()).then(()=>{console.log(2)}));

    console.log(3);

    process.nextTick(()=>{
        console.log(4);
    });

    new Promise(resolve=>resolve()).then(()=>{console.log(5)});

}

run();

我的预期输出是依次打印1,2,3,4,5的数字,但我得到:

1
2
3
5
4

按预期,第一个nextTick会在第一个.then回调之前进行求值,因为process.nextTick.then都将推迟到将来的价格变动,而process.nextTick.then之前声明。因此,12会按预期顺序输出。

直到解析await之后,代码才应该达到.then之后的状态,并且按预期方式工作,因为3输出在预期位置。

然后从本质上讲,我们重复了代码的第一部分,但是这次.thenprocess.nextTick之前被调用。

这似乎是不一致的行为。为什么process.nextTick.then回调之前第一次被调用而第二次却没有被调用?

2 个答案:

答案 0 :(得分:1)

node.js事件队列不是单个队列。实际上,它是一堆不同的队列,诸如process.nextTick()和诺言.then()处理程序的处理不在同一队列中进行。因此,不同类型的事件不一定是FIFO。

这样,如果您有大约同时发生在事件队列中的多个事件,并且希望它们以特定的顺序提供,那么确保该顺序的最简单方法就是编写代码以强制执行所需的顺序,不要试图猜测一下大约在同一时间进入队列的两件事情将如何排序。

的确,两个类型完全相同的操作(例如两个process.nextTick()操作或两个已解决的promise操作)将按照它们放入事件队列的顺序进行处理。但是,不同类型的操作可能不会按照它们放入事件队列中的相对顺序进行处理,因为不同类型的事件是在事件循环遍历所有不同类型事件的周期中的不同时间进行处理的。

可能有可能完全了解node.js中的事件循环对于每种事件类型如何工作,并确切预测大约在同一时间进入事件队列的两个事件将如何相互处理,但是这不简单。当新事件添加到事件队列时,它还取决于事件循环在其当前处理中的位置,这一事实使情况更加复杂。

就像我前面的评论中的交货示例一样,相对于其他交货,确切地处理新交货的时间取决于新订单到达队列时交货驱动程序的位置。 node.js事件系统也是如此。如果在node.js处理计时器事件时插入了新事件,则该事件与其他类型的事件的相对顺序可能不同,与之相比,node.js在插入文件时处理文件I / O完成事件时,其相对顺序有所不同。因此,由于这种复杂性,我不建议您尝试预测大约在同一时间插入事件队列的不同类型的异步事件的执行顺序。

而且,我应该补充一点,将本机promise直接插入到事件循环实现中(作为其自己的微任务类型),因此本机promise实现在原始代码中的行为可能与非本机promise实现不同。再次有一个原因,就是无法尝试准确预测事件循环如何安排彼此之间相对不同的事件类型。


如果处理顺序对您的代码很重要,请使用代码强制执行特定的完成处理顺序。

作为将事件插入事件队列时事件队列的重要性的示例,您的代码简化为:

async function run(){

    process.nextTick(()=>{
        console.log(1);
    });

    await Promise.resolve().then(()=>{console.log(2)});

    console.log(3);

    process.nextTick(()=>{
        console.log(4);
    });

    Promise.resolve().then(()=>{console.log(5)});

}

run();

生成此输出:

1
2
3
5
4

但是,只要将run()调用为Promise.resolve().then(run)并且顺序突然不同就可以更改:

async function run(){

    process.nextTick(()=>{
        console.log(1);
    });

    await Promise.resolve().then(()=>{console.log(2)});

    console.log(3);

    process.nextTick(()=>{
        console.log(4);
    });

    Promise.resolve().then(()=>{console.log(5)});

}

Promise.resolve().then(run);

生成与此完全不同的输出:

2
3
5
1
4

您可以看到,当代码从一个已解决的承诺开始时,该代码中发生的其他已解决的承诺将在.nextTick()事件之前得到处理,而从另一点开始的代码则不是这种情况在事件队列处理中。这是使事件队列系统非常难以预测的部分。


因此,如果您要保证特定的执行顺序,则必须使用所有相同类型的事件,然后它们将以相对于彼此的FIFO顺序执行,或者必须使代码强制执行您想要的订单。因此,如果您真的想查看此顺序:

1
2
3
4
5

您可以使用本质上可以映射到此的所有promise:

async function run(){

    Promise.resolve().then(() => {
        console.log(1);
    })
    Promise.resolve().then(() => {
        console.log(2)
    });

    await Promise.resolve().then(()=>{});

    console.log(3);

    Promise.resolve().then(() => {
        console.log(4)
    });

    Promise.resolve().then(()=>{console.log(5)});

}

run();

或者,您更改​​代码的结构,以使代码始终按所需顺序处理事情:

async function run(){

    process.nextTick(async ()=>{
        console.log(1);
        await Promise.resolve().then(()=>{console.log(2)});
        console.log(3);
        process.nextTick(()=>{
            console.log(4);
            Promise.resolve().then(()=>{console.log(5)});
        });
    });
}

run();

这两种情况下的任何一种都会生成输出:

1
2
3
4
5

答案 1 :(得分:0)

感谢有用的评论,这使我意识到并不是所有的javascript延迟创建的方法都是一样的。我发现(new Promise()).thenprocess.nextTick甚至setTimeout(callback,0)都将完全相同,但事实证明我无法假设。

现在,我将在这里离开,我的问题的解决方案是,如果按预期的顺序无法使用process.nextTick,则不使用它。

所以我可以将代码更改为(免责声明,这实际上通常不是很好的异步代码):

async function run(){

  process.nextTick(()=>{
    console.log(1);
  });

  await (new Promise(resolve=>resolve()).then(()=>{console.log(2)}));

  console.log(3);

  (new Promise(resolve=>resolve())).then(()=>{console.log(4)});

  (new Promise(resolve=>resolve())).then(()=>{console.log(5)});

 }

run();

现在,通过将两个4设为相同类型的异步调用,确保它们在5之前记录。使它们两个都使用process.nextTick还将确保4记录在5之前。

async function run(){

  process.nextTick(()=>{
   console.log(1);
  });

  await (new Promise(resolve=>resolve()).then(()=>{console.log(2)}));

  console.log(3);

  process.nextTick(()=>{
    console.log(4)
  });

  process.nextTick(()=>{
    console.log(5)
  });

}

run();

这是解决我的问题的一种方法,但是如果有人想对我的问题提供更直接的答案,我会很乐意接受。