Use async / await para implementar la exclusión mutua en JavaScript
Necesitaba ejecutar una secuencia de tareas asíncronas en JavaScript. Son asíncronos porque necesito que no bloqueen, pero aún quiero que la tarea actual termine antes de comenzar la siguiente. El orden de las tareas no importa, lo único importante es que se excluyen mutuamente.
Por esta razón, tal vez por el trasfondo de C / C ++, estaba a punto de implementar una cola y un sistema de bloqueos. Luego me detengo a pensar si el problema podría resolverse simplemente usando async / await, y terminé con esta solución:
previousPromise = null;
async function enqueue(task) {
while (previousPromise) {
await previousPromise;
}
previousPromise = executeTask(task);
await previousPromise;
previousPromise = null;
}
En la práctica, utilizo la cola interna de los suscriptores de promesa como cola de bloqueo, para lograr una espera sin bloqueo. Cuando la promesa se resuelve, despierta todas las tareas asincrónicas † y el primer resultado previousPromiseigual a nulo continuará.
Aquí hay un ejemplo de codepen .
¿Hay algún problema en esta solución? Gracias
Nota al margen 1 : obviamente no se necesita un sistema de bloqueo real . JavaScript es de un solo subproceso, por lo que no existe ningún riesgo de que se lo evite después del tiempo y antes de la
executeTaskasignación.Nota al margen 2 : las tareas llegan del servidor de forma asincrónica, por lo que no puedo recopilarlas todas antes de ejecutarlas.
† Realmente llama a la primera devolución de llamada en la lista de suscriptores
Respuestas
Este bucle:
async function enqueue(task) {
while (previousPromise) {
await previousPromise;
}
bloqueará (asincrónicamente) para siempre, después de que previousPromisese haya asignado la primera vez a. Una expresión que es una Promesa (por ejemplo, la previousPromise) siempre seguirá siendo una Promesa y será veraz. Puede utilizar await previousPromisepara extraer su valor de resolución en una expresión diferente, pero previousPromisesigue siendo una Promesa. No se pueden completar más de dos tareas usando eso enqueue.
Un mejor método sería reasignar la Promesa cada vez que enqueuese llama, mientras se encadena a la Promesa anterior con .then:
previousPromise = previousPromise.then(() => executeTask(task));
También será más fácil de administrar si la forma previousPromisepermanece en la misma forma (es decir, de una Promesa), en lugar de tener que administrarla posiblemente null. Inicializarlo en Promise.resolve().
Demostración a continuación, donde una tarea toma 1 segundo: comienza 2 tareas inmediatamente, luego 2 tareas más después de 1,5 segundos (por lo que estas 4 juntas terminan después de 4 segundos), luego otra después de 7 segundos (que termina en el 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);
Otra cosa a considerar es: ¿qué desea que ocurra cuando una de las tareas se rechace (si es que alguna vez existe la posibilidad)? Es posible que desee agregar un .catchal final de executeTaskpara asegurarse de que se pueda iniciar la siguiente tarea, sin que se rompa toda la cadena Promise:
previousPromise = previousPromise
.then(() => executeTask(task))
.catch((err) => { /* log error? Make sure previousPromise always resolves */ });