问题背景:
在公司的一个Node.js项目中,在async方法内部,需要调用另外一个async方法进行大文本的正则匹配(耗时非常久),之前以为只要是不添加await关键字,这个方法就可以自动的异步进行调用。但实际上,每当访问这个接口时,响应还是非常的缓慢。
Node的事件模型
如上图所示,每个请求到Node的时候,程序会把请求方法与其它方法放入一个事件队列中,然后在Node的主线程中重复循环处理函数,当遇到阻塞时(一般是磁盘IO、数据库IO、网络IO等),主线程会跳到下一个函数执行。而那个等待IO的函数则由线程池进行监听,当子线程监听到完成事件,就把该回调函数放入事件循环队列中,让主线程进行回调。
问题的理解与解答
Promise的认知
上面提到了,以不添加await的形式调用async方法。实际上,await和async是对Promise的一个语法糖的体现,其中用async声明的函数本身就是一个Promise对象,而在调用时添加await方法大致等同于在Promise后面添加一个then()的回调函数来处理Promise的结果。
解答
根据上面的描述,只要调用async不添加await关键字的方法,那么这个方法就会异步的执行,而本次事件的流程会继续往下走。那么,为什么在调用非常耗时的操作后,整个应用程序会变得异常缓慢呢?
我是这么理解的:Promise只是一个异步方法的容器,仅仅标明这个方法是异步的、一定会有结果的。但是它并不会调用线程池的线程去执行这个Promise,总的来说就是:这个Promise是由Node的主线程去执行的。这就会造成一个问题,即便当前调用耗时操作的方法没有因此阻塞,但是后续事件循环到这个耗时操作时,Node主线程还是得去处理,一旦线程处理这个耗时操作,那么整个应用程序的其它请求就无法得到响应。
实际解决方式
根据上述描述,我们需要一个真正的异步线程去执行我们的耗时函数,这个时候可以用到的模块有:
Worker threads, child processes, clusters, job queues
具体的实现可以根据Node的api文档进行。
此外,如果还不太理解上述内容,可以访问下面几条链接进一步了解:
这两个是问答,有非常直接的答案:
https://stackoverflow.com/questions/46004290/will-async-await-block-a-thread-node-js
https://www.reddit.com/r/node/comments/a5a7fe/how_to_do_work_asynchronously/
介绍耗时操作如何阻塞整个事件循环,虽然和这个问题不太相关,但是写的还不错:
https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/