una termine sincrono secuenciales que node funciones funcion esperar español await async asincrono asincronismo javascript promise async-await cancellation ecmascript-next

javascript - termine - node js asincrono



¿Hay una forma de cortocircuito asíncrono/espera flujo? (4)

Acabo de dar una charla sobre esto: este es un tema encantador, pero lamentablemente no le van a gustar las soluciones que voy a proponer, ya que son soluciones de acceso.

Lo que la especificación hace por ti

Obtener la cancelación "justo" es realmente muy difícil. La gente ha estado trabajando solo en eso por un tiempo y se decidió no bloquear las funciones asíncronas en él.

Hay dos propuestas que intentan resolver esto en el núcleo de ECMAScript:

  • Fichas de cancelación : que agregan fichas de cancelación que tienen como objetivo resolver este problema.
  • Promesa cancelable - que agrega la sintaxis catch cancel (e) { sintaxis y throw.cancel que tiene como objetivo abordar este problema.

Ambas propuestas cambiaron sustancialmente durante la última semana, por lo que no contaría con ninguna de ellas para llegar en el próximo año más o menos. Las propuestas son un tanto complementarias y no están en desacuerdo.

¿Qué puedes hacer para resolver esto desde tu lado?

Los tokens de cancelación son fáciles de implementar. Lamentablemente, el tipo de cancelación que realmente desearía (también conocida como "cancelación del tercer estado donde la cancelación no es una excepción) es imposible con las funciones asíncronas en este momento ya que no controla cómo se ejecutan. Puede hacer dos cosas:

  • En su lugar, use coroutines: Bluebird se envía con cancelación de sonido usando generadores y promesas que puede usar.
  • Implemente tokens con semántica abortiva - esto es realmente bastante fácil, así que hagámoslo aquí

CancelaciónTokens

Bueno, una señal de cancelación de señales:

class Token { constructor(fn) { this.isCancellationRequested = false; this.onCancelled = []; // actions to execute when cancelled this.onCancelled.push(() => this.isCancellationRequested = true); // expose a promise to the outside this.promise = new Promise(resolve => this.onCancelled.push(resolve)); // let the user add handlers fn(f => this.onCancelled.push(f)); } cancel() { this.onCancelled.forEach(x => x); } }

Esto te permitiría hacer algo como:

async function update(token) { if(token.isCancellationRequested) return; var urls = await getCdnUrls(); if(token.isCancellationRequested) return; var metadata = await fetchMetaData(urls); if(token.isCancellationRequested) return; var content = await fetchContent(metadata); if(token.isCancellationRequested) return; await render(content); return; } var token = new Token(); // don''t ned any special handling here update(token); // ... if(updateNotNeeded) token.cancel(); // will abort asynchronous actions

Esta es una manera realmente fea que funcionaría, de manera óptima, querría que las funciones asíncronas estén al tanto de esto, pero no lo están ( todavía ).

De manera óptima, todas sus funciones provisionales serían conscientes y se producirían una cancelación (de nuevo, solo porque no podemos tener un tercer estado) que se vería así:

async function update(token) { var urls = await getCdnUrls(token); var metadata = await fetchMetaData(urls, token); var content = await fetchContent(metadata, token); await render(content, token); return; }

Dado que cada una de nuestras funciones tiene conocimiento de la cancelación, pueden realizar una cancelación lógica real: getCdnUrls puede abortar la solicitud y lanzar, fetchMetaData puede abortar la solicitud subyacente y lanzar, y así sucesivamente.

Aquí es cómo se puede escribir getCdnUrl (note el singular) usando la API XMLHttpRequest en los navegadores:

function getCdnUrl(url, token) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); var p = new Promise((resolve, reject) => { xhr.onload = () => resolve(xhr); xhr.onerror = e => reject(new Error(e)); token.promise.then(x => { try { xhr.abort(); } catch(e) {}; // ignore abort errors reject(new Error("cancelled")); }); }); xhr.send(); return p; }

Esto es lo más cercano que podemos obtener con las funciones asíncronas sin coroutines. No es muy bonito pero ciertamente es utilizable.

Tenga en cuenta que desearía evitar que las cancelaciones se traten como excepciones. Esto significa que si sus funciones se cancelan, debe filtrar esos errores en el process.on("unhandledRejection", e => ... manejadores de errores globales process.on("unhandledRejection", e => ... y similares).

async function update() { var urls = await getCdnUrls(); var metadata = await fetchMetaData(urls); var content = await fetchContent(metadata); await render(content); return; } //All the four functions return a promise. (getCdnUrls, fetchMetaData, fetchContent, render)

¿Qué pasa si queremos abortar la secuencia desde el exterior, en cualquier momento?

Por ejemplo, cuando se ejecuta fetchMetaData, nos damos cuenta de que ya no es necesario procesar el componente y queremos cancelar las operaciones restantes (fetchContent y render). ¿Hay alguna forma de abortar / cancelar desde afuera por el consumidor?

Podríamos comprobar después de cada espera de una condición, pero eso parece una forma poco elegante de hacer esto. y todavía esperará a que finalice la operación actual.


Al igual que en el código normal, debe lanzar una excepción desde la primera función (o cada una de las funciones siguientes) y tener un bloque de prueba en todo el conjunto de llamadas. No es necesario tener extra si-elses. Ese es uno de los aspectos interesantes de async / await, en el que puedes mantener el manejo de errores de la forma en que estamos acostumbrados desde el código normal.

Al cancelar las otras operaciones no es necesario. Realmente no comenzarán hasta que el intérprete encuentre sus expresiones. Por lo tanto, la segunda llamada asíncrona solo se iniciará después de que termine la primera, sin errores. Otras tareas podrían tener la oportunidad de ejecutarse mientras tanto, pero para todos los propósitos y propósitos, esta sección de código es serial y se ejecutará en el orden deseado.


Desafortunadamente, no, no puede controlar el flujo de ejecución del comportamiento predeterminado de async / await: no significa que el problema en sí sea imposible, significa que debe cambiar un poco su enfoque.

En primer lugar, su propuesta de envolver cada línea asíncrona en un cheque es una solución que funciona, y si tiene un par de lugares con dicha funcionalidad, no hay nada de malo en ello.

Si desea usar este patrón con bastante frecuencia, la mejor solución, probablemente, es cambiar a los generadores : aunque no está tan extendido, le permiten definir el comportamiento de cada paso, y agregar cancelar es lo más fácil. Los generadores son bastante potentes , pero, como he mencionado, requieren una función de corredor y no son tan simples como async / await.

Otro enfoque es crear un patrón de tokens cancelables : crea un objeto, que se llenará con una función que desea implementar esta funcionalidad:

async function updateUser(token) { let cancelled = false; // we don''t reject, since we don''t have access to // the returned promise // so we just don''t call other functions, and reject // in the end token.cancel = () => { cancelled = true; }; const data = await wrapWithCancel(fetchData)(); const userData = await wrapWithCancel(updateUserData)(data); const userAddress = await wrapWithCancel(updateUserAddress)(userData); const marketingData = await wrapWithCancel(updateMarketingData)(userAddress); // because we''ve wrapped all functions, in case of cancellations // we''ll just fall through to this point, without calling any of // actual functions. We also can''t reject by ourselves, since // we don''t have control over returned promise if (cancelled) { throw { reason: ''cancelled'' }; } return marketingData; function wrapWithCancel(fn) { return data => { if (!cancelled) { return fn(data); } } } } const token = {}; const promise = updateUser(token); // wait some time... token.cancel(); // user will be updated any way

He escrito artículos, tanto en cancelación como en generadores:

Para resumir: tiene que hacer un trabajo adicional para respaldar la canalización, y si desea tenerlo como un ciudadano de primera clase en su solicitud, debe usar generadores.


Puede obtener lo que desee utilizando Typescript + Bluebird + cancelable-awaiter .

Ahora que todas las pruebas apuntan a tokens de cancelación que no llegan a ECMAScript , creo que la mejor solución para las cancelaciones es la implementación de bluebird mencionada por @BenjaminGruenbaum , sin embargo, el uso de co-rutinas y generadores es un poco torpe e incómodo a la vista. .

Desde que estoy usando Typescript, que ahora admite la sintaxis async / await para los objetivos es5 y es3, he creado un módulo simple que reemplaza al ayudante de __awaiter predeterminado con uno que admite cancelaciones de bluebird: cancelable-awaiter