javascript async-await es6-promise

javascript - Esperado pero nunca resuelto/rechazado uso de memoria de promesa



async-await es6-promise (2)

Hice algunas pruebas usando la siguiente estructura:

function doesntSettle() { return new Promise(function(resolve, reject) { // Never settle the promise }); } let awaited = 0; let resolved = 0; async function test() { awaited++; await doesntSettle(); resolved++; } setInterval(() => { for (let i = 0; i < 100; ++i) { test(); } }, 1);

Implementado aquí: https://codesandbox.io/s/unsetteled-awaited-promise-memory-usage-u44oc

Ejecutar solo el marco de resultados en Google Chrome mostró un aumento continuo del uso de memoria en la pestaña Memoria de herramientas de desarrollo (pero no en la pestaña Montón de rendimiento / JS), lo que indica una fuga. Ejecutando esto pero resolviendo las promesas no se filtró.

Ejecutar este aumento del uso de memoria para mí aumentó en 1-4 MB / segundo. Al detenerlo y ejecutar el GC no se liberó nada.

¿ await una Promise que ni resuelve ni rechaza (nunca se resuelve / no se cumple) provocará una pérdida de memoria?

slorber/awesome-debounce-promise curiosidad por esto mientras miraba React hooks con slorber/awesome-debounce-promise que crea nuevas promesas, pero solo resuelve la última de ellas, dejando así a muchos / más inestables / incumplidos.


Prefacio (¡probablemente lo sepas!):

await es azúcar sintáctico para usar devoluciones de llamada prometedoras. (Muy, muy, muy buen azúcar.) Una función async es una función en la que el motor de JavaScript construye las cadenas de promesa y tal para usted.

Responder:

Lo relevante no es tanto si la promesa se resuelve, sino si las devoluciones de llamada de la promesa (y las cosas a las que se refieren / cierran) se retienen en la memoria. Si bien la promesa está en memoria e inestable, tiene una referencia a sus funciones de devolución de llamada, manteniéndolas en la memoria. Dos cosas hacen que esas referencias desaparezcan:

  1. Resolviendo la promesa, o
  2. Publicar todas las referencias a la promesa, lo que lo hace elegible para GC (probablemente, más abajo)

En el caso normal, el consumidor de una promesa conecta a los manejadores con la promesa y luego no guarda una referencia en absoluto, o solo mantiene una referencia a ella en un contexto en el que las funciones del manejador se cierran y no en otra parte. (En lugar de, por ejemplo, mantener la referencia de promesa en una propiedad de objeto de larga duración).

Asumiendo que la implementación de rebote libera su referencia a la promesa de que nunca se resolverá, y el consumidor de la promesa no ha almacenado una referencia en algún lugar fuera de este ciclo de referencia mutua, entonces la promesa y los manejadores se registraron en ella (y cualquier cosa que tienen la única referencia para) se puede recolectar toda la basura una vez que se libera la referencia a la promesa.

Eso requiere un poco de cuidado por parte de la implementación. Por ejemplo (gracias Keith por marcar esto) , si la promesa usa una devolución de llamada para alguna otra API (por ejemplo, addEventListener ) y la devolución de llamada se cierra por una referencia a la promesa, ya que la otra API tiene una referencia a la devolución de llamada, eso podría evitar que se publiquen todas las referencias a la promesa y, por lo tanto, guardar en la memoria todo lo que la promesa hace referencia (como sus devoluciones de llamada).

Por lo tanto, dependerá de que la implementación sea cuidadosa y un poco del consumidor. Sería posible escribir código que mantuviera referencias a las promesas y, por lo tanto, causara una pérdida de memoria, pero en el caso normal no esperaría que el consumidor hiciera eso.