搜索

在Node.js 中同步调用异步函数的正确方式:`worker_threads`

发布网友 发布时间:2024-10-24 01:21

我来回答

1个回答

热心网友 时间:2024-11-03 00:24

同步调用异步函数即使用同步执行的方式调用执行异步函数,并正确获取异步函数执行的结果。

众所周知,Node.js 是单线程事件循环模型,异步函数需要在事件循环中经过调度后执行,因此正常情况下直接调用异步函数是无法同步获取到结果的即:

但在某些场景下我们无法使用异步函数,如开发 ESLint 插件时,目前 processor / rule 只支持同步 API,因此在与其他库或上游依赖整合时就可能出现无法使用的尴尬,例如:

异步转同步的方式至少有如下几种:

其中child_processes 的效率最低,而目前实测 node bindings 和 worker_threads macOS 上差异不大,但 Ubuntu 上 deasync 比 synckit 慢了 25 倍左右,参考 GitHub Actions 日志。

worker_threads 的完整文档具体可以查看 官网,这里介绍一下 [synckit][] 的大致实现方式。

本质上synckit 的灵感来自于 sync-threads 和 esbuild,而目前的基准测试显示 synckit 比 sync-threads 快 20 倍左右,具体原因可能有以下几点:

具体到代码,我们使用MessageChannel 创建出两个 MessagePort 实例供主线程和工作线程使用:

然后我们创建一个Worker 实例复用:

然后在worker 实现中我们使用 parentPort 来监听主线程传递进来的数据,我们的异步任务将在这里完成并在完成后通知主线程:

我们继续看一下syncFn 的创建过程:

通过上面的两个关键步骤我们就成功将异步任务转化为同步任务了,而且相对性能很快,同时不需要『难以使用』的C++ node bindings。

由于我们使用Atomics.wait 等待工作线程的通知,默认没有超时时间,所以如果异常发生在 runAsWorker 以外,那么将永远无法收到通知,例如以下 worker 的实现:

因此synckit 提供了一个 SYNCKIT_TIMEOUT 环境变量方便调试类似的问题,即如果遇到任务长时间没有结束的情况可以尝试设置如 SYNCKIT_TIMEOUT=5000 以观察 runAsWorker 是否有异常,类似的问题在开发阶段都可以解决掉。

既然在Node.js 中可以使用 worker_threads 将异步 API 转化为相对高性能的同步 API,那么在拥有 Web Workers 接口的浏览器中是否也同样可行?理论上应该是行得通的,毕竟 MessageChannel 和 Atomics 同样可以使用,因此下一步 synckit 将开始支持浏览器端的使用。

Node.js 中 worker_threads 和浏览器中 Web Workers 的引入为我们提供了提供了相对高性能的转换代码执行顺序的功能,但相对地它的性能肯定不如原生的异步 API,因此我们还是应该使用避免类似的解决方案,转而推进如 ESLint 对异步 API 的支持。

目前在使用synckit 的 ESLint 相关的包有:
声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。
E-MAIL:11247931@qq.com
Top