Skip to content

Instantly share code, notes, and snippets.

@bjouhier
Last active May 16, 2018 19:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjouhier/9882719d30fb171cdabdfdf55c05ac4f to your computer and use it in GitHub Desktop.
Save bjouhier/9882719d30fb171cdabdfdf55c05ac4f to your computer and use it in GitHub Desktop.
const wrap = require('./wrapper').wrap;
function sleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
})
}
async function f1() {
await wrap(sleep(1), __filename, 9);
throw new Error('error1');
}
async function f2() {
await wrap(f1(), __filename, 14);
}
async function f3() {
await wrap(f2(), __filename, 18);
}
f3().catch(err => console.error(err.asyncStack || err.stack));
function sleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
})
}
async function f1() {
await sleep(1);
throw new Error('error1');
}
async function f2() {
await f1();
}
async function f3() {
await f2();
}
f3().catch(err => console.error(err.stack));
exports.wrap = function(promise, file, line) {
return promise.catch(err => { throw new WrappedError(err, file, line); });
}
class WrappedError extends Error {
constructor(inner, file, line) {
super(inner.message);
this.inner = inner;
this.file = file;
this.line = line;
}
get asyncStack() {
return `${this.inner.asyncStack || this.inner.stack}
at ${this.file}:${this.line}`;
}
}
@bjouhier
Copy link
Author

bjouhier commented May 16, 2018

Quick hack to demo how full async stacktrace could be added to async/await, with the help of a very simple transpilation pass:

  • foo.js is the source file
  • foo-wrapped.js is the transpiled source
  • wrapper.js is the little extra runtime that we need.

The transpilation pass is trivial: it just replaces every await expression by await wrap(expression).

In real life, this should not be achieved through transpilation but directly baked into the JS compiler instead. This would allow to optimize and fix some glitches:

  • the wrap function would be inlined. It would not allocate a new promise. instead it would just store the file name and line number into the promise and the core Promise class would transfer this info to the error (see below) before invoking the catch handler.
  • the WrapperError class should also be eliminated because the original exception should be preserved (catch handlers often contain instanceof tests). But then we'd need a mechanism to collect the file/line pairs into an Error instance. The base Error class would also take care of injecting the collected file/line pairs when it generates its stack (and the asyncStack hack would go away).

So don't take this gist too literally. It is just some kind of pseudo code to get started. I've also omitted lots of details (like checking that promise is actually a Promise).

Note that, if handled by the compiler, the overhead is just the storage of the file/line pair into the promise when no exception is thrown.
There is a bit more overhead when an exception is thrown, but nothing dramatic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment