Skip to content

Instantly share code, notes, and snippets.

@mmarchini
Last active October 17, 2018 17:41
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 mmarchini/1f7476e0ee21a1f17832971289c3f4b5 to your computer and use it in GitHub Desktop.
Save mmarchini/1f7476e0ee21a1f17832971289c3f4b5 to your computer and use it in GitHub Desktop.
State of Postmortem with Promises in Node.js

Goals

When a promise is rejected with no catch handler attached, it should:

  • Aborting with error code for unhandled rejection to generate core-dump
  • Get stack trace from the point where the error was thrown
  • Inspecting arguments, closures and variables

Current State

No Handler & No Catch

// 01-no-handler.js
new Promise(() => { throw new Error(); });
$ node 01-no-handler.js
(node:11991) UnhandledPromiseRejectionWarning: Error
    at Promise (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/01-no-handler.js:1:89)
    at new Promise (<anonymous>)
    at Object.<anonymous> (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/01-no-handler.js:1:63)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
(node:11991) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:11991) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
$ echo $?
0
  • Aborting with error code for unhandled rejection to generate core-dump
  • Get stack trace from the point where the error was thrown
  • Inspecting arguments, closures and variables

No Handler w/ .catch()

// 02-catch-rethrow.js
new Promise(() => { throw new Error(); }).catch((err) => { console.error(err); throw err });
$ node --abort-on-uncaught-exception 02-catch-rethrow.js
Error
    at Promise (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/02-catch-rethrow.js:1:89)
    at new Promise (<anonymous>)
    at Object.<anonymous> (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/02-catch-rethrow.js:1:63)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
(node:12073) UnhandledPromiseRejectionWarning: Error
    at Promise (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/02-catch-rethrow.js:1:89)
    at new Promise (<anonymous>)
    at Object.<anonymous> (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/02-catch-rethrow.js:1:63)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
(node:12073) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:12073) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
$ echo $?
0
  • Aborting with error code for unhandled rejection to generate core-dump
  • Get stack trace from the point where the error was thrown
  • Inspecting arguments, closures and variables

Handler with process.on("unhandledRejection")

// 03-process-unhandled-rejection.js
process.on("unhandledRejection", (err, promise) => { throw err; });

new Promise(() => { throw new Error(); });
$ node --abort-on-uncaught-exception 03-process-unhandled-rejection.js
Uncaught Error

FROM
process.on (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/03-process-unhandled-rejection.js:1:116)
process.emit (events.js:182:13)
emitPromiseRejectionWarnings (internal/process/promises.js:105:20)
process._tickCallback (internal/process/next_tick.js:69:34)
Function.Module.runMain (internal/modules/cjs/loader.js:745:11)
startup (internal/bootstrap/node.js:279:19)
bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)
[1]    12084 illegal hardware instruction (core dumped)  node --abort-on-uncaught-exception 03-process-unhandled-rejection.js
$ echo $?
1
(llnode) v8 bt
 * thread #1: tid = 0x0000, 0x0000000100c23c12 node`v8::base::OS::Abort() + 18, stop reason = signal SIGSTOP
  * frame #0: 0x0000000100c23c12 node`v8::base::OS::Abort() + 18
    frame #1: 0x000000010065cf86 node`v8::internal::Isolate::Throw(v8::internal::Object*, v8::internal::MessageLocation*) + 838
    frame #2: 0x00000001007fa36b node`v8::internal::Runtime_Throw(int, v8::internal::Object**, v8::internal::Isolate*) + 59
    frame #3: 0x00000b126d7dc01d <exit>
    frame #4: 0x00000b126d82ecda <stub>
    frame #5: 0x00000b126d7918b5 process.on(this=0x000022c9cd703ba9:<Object: process>, 0x000022c912fc4119:<Object: Error>, 0x000022c912fc4031:<unknown>) at /Users/mmarchini/workspace/tmp/postmortem-promises-examples/03-process-unhandled-rejection.js:1:95 fn=0x000022c912fc3e31
    frame #6: 0x00000b126d7918b5 emit(this=0x000022c9cd703ba9:<Object: process>, 0x000022c908c5d029:<String: "unhandledRejecti...">) at events.js:140:44 fn=0x000022c95ddc4189
    frame #7: 0x00000b126d78a5a3 <adaptor>
    frame #8: 0x00000b126d7918b5 emitPromiseRejectionWarnings(this=0x000022c9e9a826f1:<undefined>) at (external).js:89:38 fn=0x000022c9445dfcf1
    frame #9: 0x00000b126d7918b5 _tickCallback(this=0x000022c9cd703ba9:<Object: process>) at (external).js:41:25 fn=0x000022c9445dead1
    frame #10: 0x00000b126d7918b5 Module.runMain(this=0x000022c912f89c11:<function: Module at (external).js:102:16>) at (external).js:729:26 fn=0x000022c908c75471
    frame #11: 0x00000b126d7918b5 startup(this=0x000022c9e9a826f1:<undefined>) at internal/bootstrap/node.js:1:10 fn=0x000022c9cd718279
    frame #12: 0x00000b126d7918b5 bootstrapNodeJSCore(this=0x000022c9e9a822b1:<null>, 0x000022c9cd703ba9:<Object: process>, 0x000022c9cd718479:<Object: Object>, 0x000022c9cd718451:<Object: Object>) at internal/bootstrap/node.js:1:10 fn=0x000022c9cd7184b1
    frame #13: 0x00000b126d78ee55 <internal>
    frame #14: 0x00000b126d789521 <entry>
    frame #15: 0x000000010054db36 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::Object>, v8::internal::Execution::MessageHandling, v8::internal::Execution::Target) + 1014
    frame #16: 0x000000010054d6a6 node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 166
    frame #17: 0x00000001001ef4ac node`v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 460
    frame #18: 0x000000010003e61a node`node::ExecuteBootstrapper(node::Environment*, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, v8::Local<v8::Value>*) + 44
    frame #19: 0x000000010003dbc2 node`node::LoadEnvironment(node::Environment*) + 907
    frame #20: 0x0000000100040de5 node`node::Start(v8::Isolate*, node::IsolateData*, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&) + 704
    frame #21: 0x000000010004033c node`node::Start(uv_loop_s*, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&) + 326
    frame #22: 0x000000010003fe1c node`node::Start(int, char**) + 705
    frame #23: 0x0000000100001034 node`start + 52
  • Aborting with error code for unhandled rejection to generate core-dump
  • Get stack trace from the point where the error was thrown 1
  • Inspecting arguments, closures and variables

Handler with isolate->SetPromiseRejectCallback

// 04-promise-reject-callback.js
const promiseReject = require('set-promise-reject-callback');

promiseReject.SetCallback(function biz(event, promise, reason) {
  throw reason;
});

function wee(lala) {
  throw new Error();
}

function foo() {
  new Promise(function bar() { wee(5); });
}

foo();
$ node --abort-on-uncaught-exception 04-promise-reject-callback.js
Uncaught Error

FROM
biz (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:4:3)
foo (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:12:3)
Object.<anonymous> (/Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:15:1)
Module._compile (internal/modules/cjs/loader.js:689:30)
Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
Module.load (internal/modules/cjs/loader.js:599:32)
tryModuleLoad (internal/modules/cjs/loader.js:538:12)
Function.Module._load (internal/modules/cjs/loader.js:530:3)
Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
startup (internal/bootstrap/node.js:279:19)
bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)
[1]    12169 illegal hardware instruction (core dumped)  node --abort-on-uncaught-exception 04-promise-reject-callback.js
$ echo $?
1
 * thread #1: tid = 0x0000, 0x0000000100c23c12 node`v8::base::OS::Abort() + 18, stop reason = signal SIGSTOP
  * frame #0: 0x0000000100c23c12 node`v8::base::OS::Abort() + 18
    frame #1: 0x000000010065cf86 node`v8::internal::Isolate::Throw(v8::internal::Object*, v8::internal::MessageLocation*) + 838
    frame #2: 0x00000001007fa36b node`v8::internal::Runtime_Throw(int, v8::internal::Object**, v8::internal::Isolate*) + 59
    frame #3: 0x00003f25c1adc01d <exit>
    frame #4: 0x00003f25c1b2f99a <stub>
    frame #5: 0x00003f25c1a918b5 biz(this=0x000026a4c439aa19:<Global proxy>, <Smi: 0>, 0x000026a4ca95c031:<unknown>, 0x000026a4ca95c119:<Object: Error>) at /Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:3:39 fn=0x000026a4ca95bfb1
    frame #6: 0x00003f25c1a8ee55 <internal>
    frame #7: 0x00003f25c1a89521 <entry>
    frame #8: 0x000000010054db36 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::Object>, v8::internal::Execution::MessageHandling, v8::internal::Execution::Target) + 1014
    frame #9: 0x000000010054d6a6 node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 166
    frame #10: 0x00000001001ef4ac node`v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 460
    frame #11: 0x0000000103383a98 binding.node`OnPromiseReject(message=PromiseRejectMessage @ 0x00007ffeefbfdfc0) at binding.cc:20 [opt]
    frame #12: 0x0000000100668892 node`v8::internal::Isolate::ReportPromiseReject(v8::internal::Handle<v8::internal::JSPromise>, v8::internal::Handle<v8::internal::Object>, v8::PromiseRejectEvent) + 146
    frame #13: 0x00000001006d7071 node`v8::internal::JSPromise::Reject(v8::internal::Handle<v8::internal::JSPromise>, v8::internal::Handle<v8::internal::Object>, bool) + 337
    frame #14: 0x000000010082e2ae node`v8::internal::Runtime_RejectPromise(int, v8::internal::Object**, v8::internal::Isolate*) + 142
    frame #15: 0x00003f25c1adc01d <exit>
    frame #16: 0x00003f25c1b30a00 (anonymous)(this=0x000026a4896826f1:<undefined>, 0x000026a4ca95c119:<Object: Error>) at (no script) fn=0x000026a4ca95c0e1
    frame #17: 0x00003f25c1b307b5 Promise(this=0x000026a489682801:<hole>, 0x000026a4ca95bff1:<function: bar at /Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:12:27>) at (no script) fn=0x000026a4dce8fcb1
    frame #18: 0x00003f25c1a8d151 <constructor>
    frame #19: 0x00003f25c1b0b900 <stub>
    frame #20: 0x00003f25c1a918b5 foo(this=0x000026a4c439aa19:<Global proxy>) at /Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:11:13 fn=0x000026a4ca943fa1
    frame #21: 0x00003f25c1a918b5 (anonymous)(this=0x000026a4ca9426e9:<Object: Object>, 0x000026a4ca9426e9:<Object: Object>, 0x000026a4ca943bd9:<function: require at (external).js:17:19>, 0x000026a4ca942631:<Object: Module>, 0x000026a4ca9419b1:<String: "/Users/mmarchini...">, 0x000026a4ca943b71:<String: "/Users/mmarchini...">) at /Users/mmarchini/workspace/tmp/postmortem-promises-examples/04-promise-reject-callback.js:1:10 fn=0x000026a4ca943b31
    frame #22: 0x00003f25c1a918b5 Module._compile(this=0x000026a4ca942631:<Object: Module>, 0x000026a4ca943319:<String: "const promiseRej...">, 0x000026a4ca9419b1:<String: "/Users/mmarchini...">) at (external).js:650:37 fn=0x000026a4fab75371
    frame #23: 0x00003f25c1a918b5 Module._extensions..js(this=0x000026a4ca912cb1:<Object: Object>, 0x000026a4ca942631:<Object: Module>, 0x000026a4ca9419b1:<String: "/Users/mmarchini...">) at (external).js:698:37 fn=0x000026a4fab753b1
    frame #24: 0x00003f25c1a918b5 Module.load(this=0x000026a4ca942631:<Object: Module>, 0x000026a4ca9419b1:<String: "/Users/mmarchini...">) at (external).js:590:33 fn=0x000026a4fab752f1
    frame #25: 0x00003f25c1a918b5 tryModuleLoad(this=0x000026a4896826f1:<undefined>, 0x000026a4ca942631:<Object: Module>, 0x000026a4ca9419b1:<String: "/Users/mmarchini...">) at (external).js:535:23 fn=0x000026a4ca909d91
    frame #26: 0x00003f25c1a918b5 Module._load(this=0x000026a4ca909c11:<function: Module at (external).js:102:16>, 0x000026a4ca940d71:<String: "/Users/mmarchini...">, 0x000026a4896822b1:<null>, 0x000026a4896828c9:<true>) at (external).js:502:24 fn=0x000026a4fab75271
    frame #27: 0x00003f25c1a918b5 Module.runMain(this=0x000026a4ca909c11:<function: Module at (external).js:102:16>) at (external).js:729:26 fn=0x000026a4fab75471
    frame #28: 0x00003f25c1a918b5 startup(this=0x000026a4896826f1:<undefined>) at internal/bootstrap/node.js:1:10 fn=0x000026a4c4398279
    frame #29: 0x00003f25c1a918b5 bootstrapNodeJSCore(this=0x000026a4896822b1:<null>, 0x000026a4c4383ba9:<Object: process>, 0x000026a4c4398479:<Object: Object>, 0x000026a4c4398451:<Object: Object>) at internal/bootstrap/node.js:1:10 fn=0x000026a4c43984b1
    frame #30: 0x00003f25c1a8ee55 <internal>
    frame #31: 0x00003f25c1a89521 <entry>
    frame #32: 0x000000010054db36 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::Object>, v8::internal::Execution::MessageHandling, v8::internal::Execution::Target) + 1014
    frame #33: 0x000000010054d6a6 node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 166
    frame #34: 0x00000001001ef4ac node`v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 460
    frame #35: 0x000000010003e61a node`node::ExecuteBootstrapper(node::Environment*, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, v8::Local<v8::Value>*) + 44
    frame #36: 0x000000010003dbc2 node`node::LoadEnvironment(node::Environment*) + 907
    frame #37: 0x0000000100040de5 node`node::Start(v8::Isolate*, node::IsolateData*, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&) + 704
    frame #38: 0x000000010004033c node`node::Start(uv_loop_s*, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&) + 326
    frame #39: 0x000000010003fe1c node`node::Start(int, char**) + 705
    frame #40: 0x0000000100001034 node`start + 52
  • Aborting with error code for unhandled rejection to generate core-dump
  • Get stack trace from the point where the error was thrown 1
  • Inspecting arguments, closures and variables

WARNING: This callback won't run if .then/await is attached to the promise.

1: Rethrow stack trace on Node.js with --abort-on-uncaught-exception doesn't print the right stack. If we call the above script without the flag, the stack will be correct. See https://chromium-review.googlesource.com/c/v8/v8/+/1153861

Conclusion

Right now there's no solution covering all Goals. The closest we have is Handler with process.on("unhandledRejection"), but it's still missing the resolution stack trace on the stack we got in llnode, which reduces our ability to debug errors (for example, we can't look at function arguments for functions in the resolution stack).

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