Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Awaited but never resolved/rejected promise memory usage [duplicate]

Will awaiting a Promise which neither resolves nor rejects (never settle/unfulfilled) cause a memory leak?

I got curious about this while looking at React hooks with slorber/awesome-debounce-promise that creates new promises, but only settles the last one of them, thus leaving many/most unsettle/unfulfilled.

like image 255
Qtax Avatar asked Aug 08 '19 12:08

Qtax


People also ask

What happens if you don't reject a promise resolve?

A promise is just an object with properties in Javascript. There's no magic to it. So failing to resolve or reject a promise just fails to ever change the state from "pending" to anything else. This doesn't cause any fundamental problem in Javascript because a promise is just a regular Javascript object.

How do you force promise to resolve?

You can use the async/await syntax or call the . then() method on a promise to wait for it to resolve. Inside of functions marked with the async keyword, you can use await to wait for the promises to resolve before continuing to the next line of the function.

What happens when you reject a promise Javascript?

The static Promise. reject function returns a Promise that is rejected. For debugging purposes and selective error catching, it is useful to make reason an instanceof Error .

What state is a promise in if it is neither resolved nor rejected?

This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future. A Promise is in one of these states: pending: initial state, neither fulfilled nor rejected.


2 Answers

Preface (you probably know this!):

await is syntactic sugar for using promise callbacks. (Really, really, really good sugar.) An async function is a function where the JavaScript engine builds the promise chains and such for you.

Answer:

The relevant thing isn't so much whether the promise is settled, but whether the promise callbacks (and the things they refer to / close over) are retained in memory. While the promise is in memory and unsettled, it has a reference to its callback functions, keeping them in memory. Two things make those references go away:

  1. Settling the promise, or
  2. Releasing all references to the promise, which makes it eligible for GC (probably, more below)

In the normal case, the consumer of a promise hooks up handlers to the promise and then either doesn't keep a reference to it at all, or only keeps a reference to it in a context that the handler functions close over and not elsewhere. (Rather than, for instance, keeping the promise reference in a long-lived object property.)

Assuming the debounce implementation releases its reference to the promise that it's never going to settle, and the consumer of the promise hasn't stored a reference somewhere outside this mutual-reference cycle, then the promise and the handlers registered to it (and anything that they hold the only reference for) can all be garbage collected once the reference to the promise is released.

That requires a fair bit of care on the part of the implementation. For instance (thanks Keith for flagging this up), if the promise uses a callback for some other API (for instance, addEventListener) and the callback closes over a reference to the promise, since the other API has a reference to the callback, that could prevent all references to the promise from being released, and thus keep anything the promise refers to (such as its callbacks) in memory.

So it'll depend on the implementation being careful, and a bit on the consumer. It would be possible to write code that would keep references to the promises, and thus cause a memory leak, but in the normal case I wouldn't expect the consumer to do that.

like image 190
T.J. Crowder Avatar answered Oct 05 '22 17:10

T.J. Crowder


I did some testing using the following structure:

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); 

Implemented here: https://codesandbox.io/s/unsetteled-awaited-promise-memory-usage-u44oc

Running just the result frame in Google Chrome showed continuously increasing memory usage in dev tools Memory tab (but not under the Performance/JS heap tab), indicating a leak. Running this but resolving the promises did not leak.

Running this increased memory usage for me increased by 1-4MB/second. Stopping it and running the GC did not free up any of it.

Google Chrome dev tools Memory tab showing increasing usage

like image 26
Qtax Avatar answered Oct 05 '22 17:10

Qtax