在单线程,同步,非递归代码中,我们可以确定对于任何给定的函数,一次只能对其进行多次调用。
但是,在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')
})
}
}
这个想法是,如果f
是异步函数,那么locked(f)
是一个执行相同操作的函数,除非在执行f
期间调用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()
循环中将f()
替换为for
,则执行需要1秒钟并获得以下内容:
f starts
f starts
f starts
f ends
f ends
f ends
(我知道这是一个相当小的问题,如果它不适合stackoverflow我道歉,但我不知道它有更好的地方。)
答案 0 :(得分:0)
所以,它是一种hacky方式,但你也可以缓存函数被调用的事实。
let didRun = false;
async function runMeOnce() {
if (didRun) return;
didRun = true;
... do stuff
}
await runMeOnce():
await runMeOnce(); // will just return;
我确信有更好的解决方案 - 但这只需要很少的努力。
答案 1 :(得分:0)
如果您偶然发现了这个问题,下面的代码正是OP想要的:
const disalowConcurrency = (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 = disalowConcurrency(someAsyncFunction)
syncAsyncFunction('I am called 1 second later')
syncAsyncFunction('I am called 2 seconds later')
您还可能希望将函数名称更改为更清晰的名称,因为Promise实际上与并发无关。
答案 2 :(得分:0)
这是我以前的回答中的装饰器:(Live Demo)
df1 <- structure(list(Category = c("A", "A", "A", "A", "A", "B", "B",
"B", "B", "B", "C", "C", "C", "C", "C"), Age = c(1.3, 2.6, 66.4,
41.3, 34.5, 4.34, 53.2, 6.23, 21.33, 44.23, 1.3, 2.6, 66.4, 41.3,
34.5)), class = "data.frame", row.names = c(NA, -15L))
用法:
function asyncBottleneck(fn, concurrency = 1) {
const queue = [];
let pending = 0;
return async (...args) => {
if (pending === concurrency) {
await new Promise((resolve) => queue.push(resolve));
}
pending++;
return fn(...args).then((value) => {
pending--;
queue.length && queue.shift()();
return value;
});
};
}