Use async / await para implementar exclusão mútua em JavaScript
Eu precisava executar uma sequência de tarefas assíncronas em JavaScript. Eles são assíncronos porque preciso que não bloqueiem, mas ainda quero que a tarefa atual termine antes de iniciar a próxima. A ordem das tarefas não importa, a única coisa importante é que elas estão em exclusão mútua entre si.
Por esse motivo, talvez por causa do background de C / C ++, eu estava prestes a implementar uma fila e um sistema de travas. Então paro para pensar se o problema poderia ser resolvido apenas usando async / await, e acabei com esta solução:
previousPromise = null;
async function enqueue(task) {
while (previousPromise) {
await previousPromise;
}
previousPromise = executeTask(task);
await previousPromise;
previousPromise = null;
}
Na prática, eu uso a fila interna de assinantes de promessa como a fila de bloqueio, a fim de obter uma espera sem bloqueio. Quando a promessa for resolvida, todas as tarefas assíncronas previousPromiseserão ativadas † e a primeira descoberta igual a nulo continuará.
Aqui está um exemplo de código .
Existe algum problema nesta solução? Obrigada
Nota lateral 1 : um sistema de bloqueio real obviamente não é necessário. O JavaScript é de thread único, portanto, não há risco de ser interrompido após o tempo e antes da
executeTaskatribuição.Nota lateral 2 : as tarefas chegam do servidor de forma assíncrona, então não posso coletar todas antes de executar.
† Na verdade, ele chama o primeiro retorno de chamada na lista de assinantes
Respostas
Este loop:
async function enqueue(task) {
while (previousPromise) {
await previousPromise;
}
irá (de forma assíncrona) bloquear para sempre, após a primeira vez previousPromiseter sido atribuída a. Uma expressão que é uma promessa (por exemplo, o previousPromise) sempre permanecerá uma promessa e será verdadeira. Você pode usar await previousPromisepara extrair seu valor de resolução em uma expressão diferente, mas previousPromisepermanece uma promessa. Não mais do que duas tarefas podem ser concluídas usando isso enqueue.
Um método melhor seria reatribuir a Promessa toda vez que enqueuefor chamada, enquanto o encadeamos na Promessa anterior com um .then:
previousPromise = previousPromise.then(() => executeTask(task));
Também será mais fácil administrar se previousPromisepermanecer na mesma forma (ou seja, de uma promessa), em vez de ter que administrar possivelmente sendo null. Inicialize-o para Promise.resolve().
Demonstração abaixo, onde uma tarefa leva 1 segundo: ela inicia 2 tarefas imediatamente, depois mais 2 tarefas após 1,5 segundos (portanto, essas 4 juntas terminam após 4 segundos), e outra após 7 segundos (que termina no segundo 8).
const executeTask = () => new Promise(resolve => setTimeout(() => {
console.log('task resolving');
resolve();
}, 1000));
let previousPromise = Promise.resolve();
function enqueue(task) {
previousPromise = previousPromise.then(() => executeTask(task));
}
console.log('start');
enqueue();
enqueue();
setTimeout(() => {
enqueue();
enqueue();
}, 1500);
setTimeout(() => {
enqueue();
}, 7000);
Outra coisa a considerar é: o que você deseja que ocorra quando uma das tarefas for rejeitada (se isso for uma possibilidade)? Você pode querer adicionar um .catchno final de executeTaskpara garantir que a próxima tarefa possa ser iniciada, sem que toda a cadeia da Promise seja quebrada:
previousPromise = previousPromise
.then(() => executeTask(task))
.catch((err) => { /* log error? Make sure previousPromise always resolves */ });