javascript - De ce VS Code se oprește la o excepție gestionată din Reject într-un Promise?


Ia acest cod, avem o promisiune care apelează o funcție ce va eșua și ar trebui să transmită eroarea mai departe către metoda catch a promisiunii.
Funcționează perfect când este rulată din terminal. Totuși, când este rulată prin vscode, explodează la (1).

function failingFunc() {
    let undef = undefined;
    return undef.nope();
}
let promise = new Promise((resolve, reject) => {
   resolve(failingFunc()); // (1) Explodes when run from vscode
});
promise.then(v => {}).catch((e: Error) => {
    console.log(e.message); // (2) Prints when run from the terminal
});

De ce este asta?

pagina despre vscode:
Versiune 1.14.2
Commit cb82feb
Data 2017-07-19T23:26:08.116Z
Shell 1.6.6
Renderer 56.0.2924.87
Node 7.4.0

Răspuns #1

Soluție

Așa cum a subliniat @T.J.Crowder în comentarii:

Acest lucru se întâmplă doar atunci când o excepție este aruncată cauzând o respingere înainte ca un handler să fie atașat. De exemplu, acest lucru nu ar cauza problema, deoarece atunci când excepția este convertită în respingere, există deja un handler de respingere atașat:

new Promise((resolve, reject) => setTimeout(() => { 
    try { 
        throw new Error(); 
    } catch (e) { 
        reject(e); 
    } 
}, 0)).catch(error => console.log("Error:", error));

Se pare că acesta este un „bug” cunoscut atunci când folosești VSCode pentru a depana Node.js. Așa cum este explicat în această problemă (la depozitul Git al VSCode), acest lucru se întâmplă deoarece Node.js trimite un eveniment de întrerupere cu o excepție undefined de fiecare dată când întâlnește un callback reject. Când debugger-ul din VSCode vede acest eveniment de întrerupere, face ceea ce ar trebui să facă cu excepțiile necunoscute: întrerupe execuția și apoi aruncă excepția.
Mai mult, în această problemă (la depozitul vscode-node-debug2), @roblourens spune că:

Dacă o promisiune este refuzată înainte ca un handler de erori să fie atașat, debugger-ul se va opri, chiar dacă este bifată doar opțiunea „excepții neprinse”. Dacă este refuzată după ce handler-ul de erori este atașat, funcționează conform așteptărilor. Și, într-adevăr, problema este modul în care promisiunea nu știe dacă refuzul ei va fi gestionat sau nu.

Încă poți folosi vscode pentru dezvoltarea sistemelor bazate pe Promise, totuși va trebui să dezactivezi toate gestionările de erori în vscode, așa cum se vede mai jos, asigură-te că niciuna dintre opțiuni nu este bifată.

NOTĂ: Deoarece aceasta este departe de a fi o soluție optimă, este probabil să fie modificată și/sau îmbunătățită în viitor.

(Am comentat pe problema din vscode și voi actualiza această postare dacă aflu ceva util)

Edit1:
Am descoperit că o altă soluție este să definești o combinație de taste în vscode pentru a rula comanda workbench.action.debug.run. Aceasta va rula opțiunea de depanare selectată în prezent fără a atașa depanatorul la aceasta. Aceasta înseamnă că poți păstra depanatorul cu setările tale normale, rulând codul folosind noua comandă de tastatură atunci când trebuie să lucrezi cu promisiuni respinse.

/* keybindings.json */
[
    {
        "key": "ctrl+shift+b",
        "command": "workbench.action.debug.start"
        /* Attaches debugger */
    },
    {
        "key": "ctrl+b",
        "command": "workbench.action.debug.run"
        /* Runs without debugger */
    }
]

Edit2:
Așa cum @T.J.Crowder a subliniat în comentarii:

Acest lucru se întâmplă doar atunci când o excepție este aruncată cauzând o respingere înainte ca un handler să fie atașat. De exemplu, acest lucru nu ar provoca-o, deoarece atunci când excepția este convertită în respingere, există deja un handler de respingere atașat:

new Promise((resolve, reject) => setTimeout(() => { 
    try { 
        throw new Error(); 
    } catch (e) { 
        reject(e); 
    } 
}, 0)).catch(error => console.log("Error:", error));

Și desigur că avea dreptate. Codul de mai jos funcționează în vscode cu depanatorul atașat.

function failingFunc() {
    let undef = undefined;
    return undef.nope();
}
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        try {
            resolve(failingFunc())
        } catch (e) {
            reject(e);
        }
    }, 0);
});
promise.then(v => {}).catch((e: Error) => {
    console.log(e.message); // Cannot read property 'nope' of undefined
});