Búsqueda de sitios web

javascript - ¿Por qué VS Code falla en una excepción manejada de Reject en Promise?


Toma este código, tenemos una promesa que llama a una función que fallará, y debería pasar el error a continuación al método catch de la promesa.
Funciona perfectamente cuando se ejecuta desde el terminal. Sin embargo, cuando se ejecuta a través de VSCode, explota en (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
});

¿Por qué es esto?

página acerca de vscode:
Versión 1.14.2
Commit cb82feb
Fecha 2017-07-19T23:26:08.116Z
Shell 1.6.6
Renderer 56.0.2924.87
Node 7.4.0

Respuesta #1

Solución

Como @T.J.Crowder señaló en los comentarios:

Esto sólo ocurre cuando se lanza una excepción que provoca un rechazo antes de que se adjunte un manejador. Por ejemplo, esto no lo causaría, porque cuando la excepción se convierte en rechazo, ya hay un manejador de rechazo adjunto:

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

Resulta que este es un "error" conocido al usar vscode para depurar Nodejs. Como se explica en este problema (en el repositorio de vscode en git), esto sucede porque Nodejs envía un evento de interrupción con una excepción undefined cada vez que encuentra un callback reject. Cuando el depurador de vscode ve este evento de interrupción, hace lo que se supone que debe hacer con excepciones desconocidas: pausa la ejecución y luego lanza la excepción.
Además, en este problema (en el repositorio vscode-node-debug2), @roblourens dice que:

Si una promesa es rechazada antes de que se adjunte un manejador de errores, el depurador se detendrá, incluso si solo está marcada la opción de "excepciones no capturadas". Si se rechaza después de que se adjunta el manejador de errores, funciona como se espera. Y realmente el problema es la forma en que la promesa no sabe si su rechazo será manejado o no.

Aún puedes usar vscode para desarrollar sistemas basados en Promise, sin embargo, necesitarás desactivar todo el manejo de errores en vscode, como se ve a continuación asegúrate de que ninguna de las opciones esté marcada.

NOTA: Dado que esto está lejos de ser una solución óptima, es probable que se cambie y/o mejore en el futuro.

(He comentado en el problema de vscode y actualizaré esta publicación si aprendo algo útil)

Edit1:
He descubierto que otra solución alternativa es definir una combinación de teclas en vscode para ejecutar el comando workbench.action.debug.run. Esto ejecutará la opción de depuración seleccionada actualmente sin adjuntar el depurador a ella. Esto significa que puedes mantener el depurador en tu configuración normal, mientras ejecutas el código usando la nueva combinación de teclas cuando necesites trabajar con promesas rechazadas.

/* 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:
Como @T.J.Crowder señaló en los comentarios:

Esto sólo ocurre cuando se lanza una excepción que provoca un rechazo antes de que se adjunte un manejador. Por ejemplo, esto no lo causaría, porque cuando la excepción se convierte en rechazo, ya hay un manejador de rechazo adjunto:

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

Y por supuesto que tenía razón. El código a continuación funciona en vscode con el depurador adjunto.

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
});