Constructor of a custom promise class is called twice (extending standard Promise)

2894 views javascript
1

I'm playing with Promise Extensions for JavaScript (prex) library. I wanted to extend the standard Promise class with cancellation support, similar to how it is implemented in Bluebird but using prex.CancellationToken.

Unexpectedly, I'm seeing the constructor of my custom class CancellablePromise being called twice. To simplify things, I've now stripped down all the cancellation logic and left just a bare minimum required to repro the issue:

class CancellablePromise extends Promise {
  constructor(executor) {
    console.log("CancellablePromise::constructor");
    super(executor);
  }
}

function delayWithCancellation(timeoutMs, token) {
  // TODO: we've stripped all cancellation logic for now
  console.log("delayWithCancellation");
  return new CancellablePromise(resolve => {
    setTimeout(resolve, timeoutMs);
  }, token);
}

async function main() {
  await delayWithCancellation(2000, null);
  console.log("successfully delayed.");
}

main().catch(e => console.log(e));

Running it with node simple-test.js, I'm getting this:

delayWithCancellation
CancellablePromise::constructor
CancellablePromise::constructor
successfully delayed.

Why are there two invocations of CancellablePromise::constructor?

I tried setting breakpoints with VSCode. The stack trace for the second hit shows it's called from runMicrotasks, which itself is called from _tickCallback somewhere inside Node.

My Node version is v10.12.0. The complete source code is here.

answered question

after console.log("CancellablePromise::constructor"); add console.log(executor+'') - and all will be revealed

@Bravo, it's a different version of executor, not mine: function () { [native code] }

what does await do?

@Bravo thanks for pointing that out. I suspect it will be the implicit Promise.resolve() call made by await but will need to check it out before getting back.

it's more an implicit .then - but the internals of async/await are not important - remove the await (but keep the async) and while now the code isn't behaving correctly, you'll see only a single CancellablePromise::constructor call

@Bravo, I have no specific knowledge of await implementation details in V8. In C# is though, it's implemented via state machine and continuation callbacks. I imagine something similar is done in V8.

1 Answer

13

.catch( callback) returns a new, pending promise of the extended Promise class it is called on an instance of. This promise was created in the second call logged from your 'CancellablePromise ' constluctor.

posted this

Have an answer?

JD

Please login first before posting an answer.