Skip to content

Instantly share code, notes, and snippets.

@lll000111
Last active July 4, 2019 20:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lll000111/3d6a53da4d53beb909189793c7631ba6 to your computer and use it in GitHub Desktop.
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)
// @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