javascript - Ce se întâmplă cu acest lanț de Promisiuni?


Îmi scarpin capul încercând să înțeleg ce se întâmplă cu următoarele cazuri.

Cazul 1:

async function doSomething(): Promise<void> {
    return new Promise(resolve => {

        const promise = (async () => "A")();

        promise
            .then(() => {
                console.log("THEN");
                resolve();
            })
            .catch(() => {
                console.log("CATCH");
            })
            .finally(() => {
                console.log("FINALLY");
            });
    });
}

async function main() {
    await doSomething();
    console.log("DONE");
}

main().catch(console.error);

Ieșire, după cum era de așteptat:

THEN
FINALLY
DONE

Dar în cazul 2:

async function doSomething(): Promise<void> {
    return new Promise(resolve => {

        const promise = (async () => Promise.resolve("A"))();

        promise
            .then(() => {
                console.log("THEN");
                resolve();
            })
            .catch(() => {
                console.log("CATCH");
            })
            .finally(() => {
                console.log("FINALLY");
            });
    });
}

async function main() {
    await doSomething();
    console.log("DONE");
}

main().catch(console.error);

Rezultatul este:

THEN
DONE
FINALLY

Și cazul 3:

async function doSomething(): Promise<void> {
    return new Promise(resolve => {

        const promise = (async () => Promise.resolve("A"))();

        promise
            .then(() => {
                console.log("THEN");
                resolve();
            })
            .finally(() => {
                console.log("FINALLY");
            });
    });
}

async function main() {
    await doSomething();
    console.log("DONE");
}

main().catch(console.error);

Rezultatul este:

THEN
FINALLY
DONE

Ce se întâmplă exact cu lanțul de promisiuni?

De ce cazul 2 nu afișează „ATUNCI, ÎN FINAL, TERMINAT”?

De ce cazul 3 se comportă diferit față de cazul 2?

Răspuns #1

S-ar putea să fi înțeles ce s-a întâmplat cu cele 3 cazuri.

Încărcare rapidă

Diferența în numărul de promisiuni și rezolvarea promisiunilor cu promisiuni schimbă ordinea microtask-urilor puse în coadă, cauzând diferența în declarațiile de tipărire.

Simplificarea codului

În primul rând, să încercăm să simplificăm codul eliminând async/await. Voi folosi codul din cazul 1 pentru simplificare.

Blocul principal

async function doSomething(): Promise<void> {
    return new Promise(resolve => {
        ...
    });
}
async function main() {
    await doSomething();
    console.log("DONE");
}

main().catch(console.error);

poate fi rescris astfel:

function doSomething(): Promise<void> {
   // Promise A
   return new Promise(resolve => {
     // Promise B
     const b = new Promise(resolve => {
            ...
     });
     resolve(b);
   });
}

doSomething()
 .then(function externalThen() {
   console.log("DONE");
 });

Declarația promise pentru cazul 1:

const promise = (async () => "A")();

devine :

const promise = Promise.resolve("A");

și pentru cazurile 2 și 3:

const promise = (async () => Promise.resolve("A"))();

devine :

const promise = new Promise(resolve => {
         resolve(Promise.resolve("A"))
});

Punând totul împreună și eliminând funcțiile și declarațiile de variabile inutile:

// Promise A
new Promise(resolve => {
    // Promise B
    const b = new Promise<void>(resolve => {
        // Promise C
        new Promise(resolve => {
            resolve("A");
        })
        .then(function then1() {
            console.log("THEN");
            resolve();
        })
        .catch(function catch1() {
            console.log("CATCH");
        })
        .finally(function finally1() {
            console.log("FINALLY");
        });
    });
    resolve(b);
})
.then(function externalThen() {
    console.log("DONE");
});

Pentru cazurile 2 și 3, declarația Promise C devine:

// Promise C
new Promise(resolve => {
    // Promise D
    resolve(Promise.resolve("A"));
})

Cum este executat codul Promise

  • funcția executor transmisă constructorului Promise este executată imediat.
  • callback-urile transmise metodelor then, catch și finally sunt executate ca microtask atunci când promisiunea se îndeplinește.
  • microtask este adăugat în o coadă de microtask
  • coada de micro-sarcini este golită și toate sarcinile sunt executate, în ordinea FIFO, când stiva JS este goală
  • trecerea unui Promise p1 la callback-ul resolve al unui Promise p2 generează o nouă microactivitate care leagă p1 de p2. Metoda then a p1 este apelată trecând callback-urile resolve și reject ale p2.

Caz de executare 1

// Promise A
new Promise(resolve => {
    // Promise B
    const b = new Promise<void>(resolve => {
        // Promise C
        new Promise(resolve => {
            resolve("A");
        })
        .then(function then1() {
            console.log("THEN");
            resolve();
        })
        .catch(function catch1() {
            console.log("CATCH");
        })
        .finally(function finally1() {
            console.log("FINALLY");
        });
    });
    resolve(b);
})
.then(function externalThen() {
    console.log("DONE");
});
  1. executarea parcurge constructorii Promise pentru Promise A, B și C
  2. C este rezolvat imediat
  3. executarea callback-ului then1 este adăugată în coada microtask-urilor
  4. Execuția continuă să lanțeze promisiunile până la rezolvarea (b)
  5. b este o Promisiune care este transmisă ca valoare de rezolvare pentru Promisiunea A. Acest lucru generează o nouă micro-sarcină care leagă B de A.
  6. Sfârșitul blocului principal, două microtask-uri în coadă: then1, chain-B-to-A.
  7. executarea then1:

    1. afișează în consolă "THEN"
    2. rezolvă Promise b
    3. Promise-ul generat de then se rezolvă și pune în coadă o micro-sarcină pentru catch1
  8. execuția lanțului-B-la-A:

    1. în pseudo-cod: b.then(a.resolve, a.reject)
    2. Promisiunea B este deja rezolvată, prin urmare micro-sarcina then-B-A este pusă în coadă
  9. coada microtask-urilor în acest moment: catch, then-B-A
  10. executarea catch1:

    1. nimic de-a face cu apelul înapoi
    2. Promisiunea generată de catch se rezolvă și pune în coadă o micro-sarcină pentru finally1
  11. executarea lui then-B-A:

    1. Promisiunea A se rezolvă și pune în coadă o micro-sarcină pentru externalThen
  12. coada microtask-urilor în acest moment: finally1, externalThen
  13. executarea finally1: afișează "FINALLY"
  14. executarea externalThen: afișează "GATA"
  15. fără microtask-uri, codul se termină

Caz de executare 2

// Promise A
new Promise(resolve => {
    // Promise B
    const b = new Promise<void>(resolve => {
        // Promise C
        new Promise(resolve => {
           // Promise D
           resolve(Promise.resolve("A"));
        })
        .then(function then1() {
            console.log("THEN");
            resolve();
        })
        .catch(function catch1() {
            console.log("CATCH");
        })
        .finally(function finally1() {
            console.log("FINALLY");
        });
    });
    resolve(b);
})
.then(function externalThen() {
    console.log("DONE");
});
  1. executarea parcurge constructorii Promise pentru Promise A, B și C

  2. Promisiunea C este rezolvată cu Promisiunea D, aceasta generează o micro-sarcină pentru a lega D de C

  3. Execuția continuă să lanțeze promisiunile până la rezolvarea (b)

  4. b este o Promisiune care este transmisă ca valoare de rezolvare pentru Promisiunea A. Acest lucru generează o nouă micro-sarcină care leagă B de A.

  5. Sfârșitul blocului principal, două microtaskuri în coadă: chain-D-to-C, chain-B-to-A.

  6. executarea chain-D-to-C:

    1. în pseudo-cod: d.then(c.resolve, c.reject)
    2. micro-sarcina then1 este pusă în coadă
  7. execuția lanțului-B-la-A:

    1. în pseudo-cod: b.then(a.resolve, a.reject)
    2. Promisiunea B nu este rezolvată, nu se întâmplă nimic altceva
  8. coada microtask în acest moment: then1

  9. executarea then1:

    1. afișează în consolă "THEN"
    2. rezolvă Promise b, aceasta pune în coadă microtask-ul then-B-A
    3. Promisiunea generată de then se rezolvă și pune în coadă o micro-sarcină pentru catch1
  10. coada microtask în acest moment: then-B-A, catch1

  11. executarea lui then-B-A:

    1. Promisiunea A se rezolvă și pune în coadă o micro-sarcină pentru externalThen
  12. executarea catch1:

    1. nimic de-a face cu apelul înapoi
    2. Promisiunea generată de catch se rezolvă și pune în coadă o micro-sarcină pentru finally1
  13. coada microtask-urilor în acest moment: externalThen, finally1

  14. executarea externalThen: afișează "GATA"

  15. executarea finally1: afișează "FINALLY"

  16. fără microtask-uri, codul se termină

Caz de executare 3

// Promise A
new Promise(resolve => {
    // Promise B
    const b = new Promise<void>(resolve => {
        // Promise C
        new Promise(resolve => {
           // Promise D
           resolve(Promise.resolve("A"));
        })
        .then(function then1() {
            console.log("THEN");
            resolve();
        })
        .finally(function finally1() {
            console.log("FINALLY");
        });
    });
    resolve(b);
})
.then(function externalThen() {
    console.log("DONE");
});

Tot identic cu cazul 2 până aici:

  1. coada microtask în acest moment: then1
  2. executarea then1:

    1. afișează în consolă "APOI"
    2. rezolvă Promise b, aceasta pune în coadă microtask-ul then-B-A
    3. Promisiunea generată de then se rezolvă și pune în coadă o micro-sarcină pentru finally1
  3. coada microtask în acest moment: then-B-A, finally1
  4. executarea lui then-B-A:

    1. Promise A se rezolvă și adaugă în coadă o micro-sarcină pentru externalThen
  5. executarea finally1: afișează "FINALLY"
  6. coada microtask în acest moment: externalThen
  7. executarea externalThen: afișează "GATA"
  8. fără microtask-uri, codul se termină