Last active
July 4, 2019 20:42
-
-
Save lll000111/3d6a53da4d53beb909189793c7631ba6 to your computer and use it in GitHub Desktop.
How to get stacktraces from errors from asynchronous functions (node.js and all browsers - simple)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
'use strict'; | |
// Needed to create a _really_ asynchronous function. A fake asynchronous function (returning | |
// "Promise.reject()" right away, for example) actually creates a stacktrace. | |
const wait = delay => new Promise(resolve => setTimeout(() => resolve(), delay)); | |
// Some deeply nested (in the actual call hierarchy) asynchronous function that throws an error. | |
const fnError = async () => { | |
await wait(1); | |
throw new Error('Ooohhhh my god, this went wrong!'); | |
}; | |
// Method: Always catch errors, never return the promise directly (because then you cannot add the local | |
// stack trace). Add a locally generated stack trace to the one already existing on the error object | |
// DISADVANTAGE: Capturing a stack trace is an expensive operation. But this is for asynchronous | |
// operations that already are much more expensive. | |
// DO NOT USE EXCEPIONS FOR NORMAL CONTROL FLOW - if exceptions truly are "exceptional" the runtime | |
// overhead of stacktrace creation won't matter in any case. | |
// SYNTAX ALTERNATIVE | |
// Instead of try/catch you can also use a promise-method based version, which | |
// has the advantage to allow assignment to "const": | |
// | |
// const result = fnError().catch(err => { | |
// err.stack += (new Error()).stack; | |
// throw err; | |
// }); | |
// | |
// The try/catch based version below requires a "let" variable definition above | |
// and therefore outside the try/catch. | |
// | |
// HOWEVER, the Promise.catch() based version creates a slightly different stacktrace: | |
// Without the local function name, but you still get the correct location (line number). | |
// That means the best stack trace will be obtained with try/catch. | |
const fnA2 = async () => { | |
// No stack trace: simply write: | |
// return fnError(); | |
// Create full stack trace: | |
let result; | |
try { | |
result = await fnError(); | |
} catch(err) { | |
err.stack += (new Error()).stack; | |
throw err; | |
} | |
// Do stuff with result | |
return result; | |
}; | |
const fnA1 = async () => { | |
// No stack trace: simply write: | |
// return fnA2(); | |
// Create full stack trace: | |
try { | |
// No more processing of the result in this function - return it (after waiting for it!!! | |
// Otherwise this function could not catch the error!) | |
return await fnA2(); | |
} catch(err) { | |
err.stack += (new Error()).stack; | |
throw err; | |
} | |
}; | |
const fnA0 = async () => { | |
// No stack trace: simply write: | |
// return fnA1(); | |
// Create full stack trace: | |
try { | |
// Yes - "return await"! Otherwise the error won't be thrown in this runtime context | |
// and the entire try/catch will only catch errors occuring during (the synchronously | |
// occuring) promise creation, but not in the actual asynchronous code. | |
return await fnA1(); | |
} catch(err) { | |
err.stack += (new Error()).stack; | |
throw err; | |
} | |
}; | |
fnA0().catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment