Node.js: предотвращение множественных одновременных вызовов асинхронной функции - PullRequest
0 голосов
/ 27 апреля 2018

В однопоточном, синхронном, нерекурсивном коде мы можем быть уверены, что для любой данной функции никогда не выполняется более одного вызова за раз.

Однако в мире 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

(Я понимаю, что это довольно незначительный вопрос, и если он не подходит для стекового потока, я прошу прощения, но я не знал лучшего места для него.)

1 Ответ

0 голосов
/ 27 апреля 2018

Итак, это был бы хакерский способ сделать это, но вы также могли бы просто кешировать факт вызова функции.

let didRun = false;

async function runMeOnce() {
  if (didRun) return;
  didRun = true;

  ... do stuff
}

await runMeOnce():
await runMeOnce(); // will just return;

Я уверен, что есть гораздо лучшие решения - но это будет работать без особых усилий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...