Search code examples
javascriptcompilationv8

v8 closures of promises questions on comment in source code


Was just looking at the v8 compiler specifically the line in promise-all-element-closure.tq : and I came across the lines.

    // Promise.allSettled, for each input element, has both a resolve and a
    // reject closure that share an [[AlreadyCalled]] boolean. That is, the
    // input element can only be settled once: after resolve is called, reject
    // returns early, and vice versa. Using {function}'s context as the marker
    // only tracks per-closure instead of per-element. When the second
    // resolve/reject closure is called on the same index, values.object[index]
    // will already exist and will not be the hole value. In that case, return
    // early. Everything up to this point is not yet observable to user code.
    // This is not a problem for Promise.all since Promise.all has a single
    // resolve closure (no reject) per element.

This seams like a bug fix. I don't quite understand how Promise.allSettled would possibly call resolve reject on the same element more then once? What was the problem?


Solution

  • I don't quite understand how Promise.allSettled would possibly call resolve reject on the same element more then once?

    Promise.allSettled doesn't call them at all. The code creating and settling the promise does — and can call them more than once. So Promise.allSettled just handles that case by returning early from the function you saw that comment in. In the case where it hasn't seen a settlement for the promise at that index, it continues so it can fill the how in the array it's filling in.

    Here's an example of a function that either A) calls reject once, or B) calls resolve at least once (possibly more than once) and also calls reject:

    function findMatchInStream(stream, text) {
        return new Promise((resolve, reject) => {
            stream.on("line", line => {
                if (line.includes(text)) {
                    resolve(line);
                }
            });
            stream.on("end", () => reject());
        });
    }
    

    That code responds to a Node.js-like line event by checking the line for the given text and, if it's found, calling resolve — even if it's already called resolve previously. Similarly, when it reaches the end of the stream, it calls reject, even if it's called resolve previously. That's okay (though to my mind, poor) because once you've settled the promise, you can't settle it again; subsequent calls to resolve or reject are ignored.

    Sadly, I've seen code just like this in the wild.