简体   繁体   中英

Multiple chains of then on a JS Promise?

Today I tried out what happens when I add multiple chains of then to a Promise. My test code looks like that (TypeScript)

class Test {

    private simulateHeavyStuff(time: number){
        // console.log("-"+time+"-");
        var start = new Date().getTime();
        var end = start;
        while(end < start + time) {
            end = new Date().getTime();
        }
    }

    test(): Promise<void> {
        let promise = new Promise<void>((resolve, reject) => {
            resolve();
        });

        // ##### ADDING CHAIN 1 #####
        promise
        .then(()=>console.log("then1"))
        .then(()=>this.simulateHeavyStuff(2000))
        .then(()=>console.log("then11"))
        .catch(console.error)

        return promise;
    }

}

let t = new Test();

// ##### ADDING CHAIN 2 #####
t.test()
.then(()=>console.log("then2"))
.then(()=>console.log("then21"))
.catch(console.error)

This prints out:

then1
then2
        <---it's pausing here
then21
then11

Can someone explain the determinism behind this behavior?

I thought it would either print (parallel chains)

then1
then2
then21
       <--- I guessed the pause will be here
then11

or (one chain)

then1
       <--- with the pause here
then11
then2
then21

OK, I guess I understand what happens after some clarifications of Jaromanda X down in the comments!

It is fully deterministic!

Promises add the callbacks to the so called " Microtasks " stack. Other than other JS stacks those Microtasks run when the JS stack is empty (all synchronous code is done)...see the Video posted by Jaromanda X in the comments!

So it happens like this:

  • "then1" is added to the MT Stack
  • test() returns
  • "then2" is added to the MT Stack

Synchronous code is done! Microtasks time!

The MT Stack looks like this now

then1
then2

So then1 is run and adds another Microtask to the MT stack as it returns a Promise. The MT stack looks like this now

then2
heavyStuff(2000)

As further Microtasks are run until the MT stack is empty this goes on until all MTs are finished. Then any other code goes on...

This is because your method test returns promise variable, that is not the same as result returned by catch in it, so when you call .test() , it will not wait the whole chain inside test .

If you change

promise
    .then(()=>console.log("then1"))
    .then(()=>this.simulateHeavyStuff(2000))
    .then(()=>console.log("then11"))
    .catch(console.error)

to

promise = promise
    .then(()=>console.log("then1"))
    .then(()=>this.simulateHeavyStuff(2000))
    .then(()=>console.log("then11"))
    .catch(console.error)

it will work as you expected.

The answer seems to be that you—probably by mistake—created two promises (since .then creates a new promise object based on the previous one, see MDN ). Have a look at the comments below:

test(): Promise<void> {
  // this creates the base promise "A"
  let promise = new Promise((resolve, reject) => {
        resolve();
    });
  // this creates promise "A1" by adding a chain of operations to "A"
  // you are not returning it however
  promise
    .then(()=>console.log("then11"))
    .then(()=>console.log("then12"))
    .then(()=>this.simulateHeavyStuff(2000))
    .then(()=>console.log("then14"))
    .catch(console.error)

  // this returns "A" (not "A1")
    return promise;

}



// this creates promise "A2"
t.test()
  .then(()=>console.log("then2"))
  .then(()=>console.log("then21"))
  .catch(console.error)

When you run the code snippet you will see that the processing of both promises seems to be done fully deterministically by the round-robin principle (meaning that the promises are executed in turn for every .then operation step). The sequence is

"A1" -> "A2" -> "A1" ...

 class Test { simulateHeavyStuff(time){ // console.log("-"+time+"-"); var start = new Date().getTime(); var end = start; while(end < start + time) { end = new Date().getTime(); } console.log('then13 (heavy stuff)'); } test() { let promise = new Promise((resolve, reject) => { resolve(); }); // ##### ADDING CHAIN 1 ##### promise .then(()=>console.log("then11")) .then(()=>console.log("then12")) .then(()=>this.simulateHeavyStuff(2000)) .then(()=>console.log("then14")) .catch(console.error) return promise; } } let t = new Test(); // ##### ADDING CHAIN 2 ##### t.test() .then(()=>console.log('then21')) .then(()=>console.log('then22')) .then(()=>console.log('then23')) .catch(console.error) 

In your case—where the execution steps are all fully synchronous—the program is being executed deterministically: ie the i -th chained .then operation of promise "A1" precedes the i -th chained .then operation of "A2"; and the i+1 chained .then operation of promise "A1" follows the i -th chained .then operation of promise "A2".

The ECMA Script 2015 specification seems to confirm this behavior since the .then pools are supposed to queued in a job queue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM