Node.js:防止同时调用异步函数

时间:2018-04-27 17:44:34

标签: javascript node.js asynchronous

在单线程,同步,非递归代码中,我们可以确定对于任何给定的函数,一次只能对其进行多次调用。

但是,在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我道歉,但我不知道它有更好的地方。)

3 个答案:

答案 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;
    });
  };
}