javascript - Node.js:防止异步函数的多个同时调用
问题描述
在单线程、同步、非递归代码中,我们可以确定对于任何给定函数,一次不会超过一次调用它。
然而,在async/await
世界上,上述情况不再适用:当我们在执行异步函数 f 期间等待某些东西时,它可能会再次被调用。
我突然想到,使用事件发射器和队列,我们可以为异步函数编写一个包装器,以保证它一次不会有多个调用。像这样的东西:
const events = require('events')
function locked(async_fn) {
const queue = [] // either actively running or waiting to run
const omega = new events()
omega.on('foo', () => {
if (queue.length > 0) {
queue[0].emit('bar')
}
})
return function(...args) {
return new Promise((resolve) => {
const alpha = new events()
queue.push(alpha)
alpha.on('bar', async () => {
resolve(await async_fn(...args))
queue.shift()
omega.emit('foo')
})
if (queue.length === 1) omega.emit('foo')
})
}
}
这个想法是 iff
是一个异步函数 thenlocked(f)
是一个做同样事情的函数,除了 iff
在执行期间被调用f
,新的调用不会开始,直到第一个调用返回。
我怀疑我的解决方案还有很大的改进空间,所以我想知道:有没有更好的方法来做到这一点?事实上,有没有一个已经内置到 Node 中,或者可以通过 npm 获得?
编辑以显示如何使用它:
async function f() {
console.log('f starts')
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('f ends')
}
const g = locked(f)
for (let i = 0; i < 3; i++) {
g()
}
运行这个需要 3 秒,我们得到以下输出:
f starts
f ends
f starts
f ends
f starts
f ends
而如果我们在循环中g()
替换为,执行需要 1 秒并得到以下结果:f()
for
f starts
f starts
f starts
f ends
f ends
f ends
(我意识到这是一个相当小的问题,如果它不适合stackoverflow,我很抱歉,但我不知道有更好的地方。)
解决方案
如果您偶然发现这个问题,这里的代码正是 OP 想要的:
const disallowConcurrency = (fn) => {
let inprogressPromise = Promise.resolve()
return async (...args) => {
await inprogressPromise
inprogressPromise = inprogressPromise.then(() => fn(...args))
return inprogressPromise
}
}
像这样使用它:
const someAsyncFunction = async (arg) => {
await new Promise( res => setTimeout(res, 1000))
console.log(arg)
}
const syncAsyncFunction = disallowConcurrency(someAsyncFunction)
syncAsyncFunction('I am called 1 second later')
syncAsyncFunction('I am called 2 seconds later')
您可能还希望将函数名称更改为更清晰的名称,因为承诺实际上与并发无关。
推荐阅读
- node.js - 续集插入行多对多关系
- python - 使用键盘中断停止所有在队列上工作的线程
- javascript - 大家好,如何增加存储在数组对象中的状态值
- azure - Azure 数据流 Actives 如何按顺序或并行运行
- r - 来自不同来源的缺失/可用数据的可视化
- java - 为什么 wait() 函数不向其他线程发送控制?
- java - 如何在用户选择不同的项目之前保持选定的项目在微调器中选择..?
- python - 如何为kivy应用程序设置最小窗口大小?
- javascript - 一名玩家可以在赠品命令中多次获胜(Discord.js)
- laravel - 如何在laravel中获取一个数组对象