Skip to content

Instantly share code, notes, and snippets.

@kriskowal
Last active August 29, 2015 13:56
Show Gist options
  • Save kriskowal/8948969 to your computer and use it in GitHub Desktop.
Save kriskowal/8948969 to your computer and use it in GitHub Desktop.
Promise Inspector Protocol

One of the features of promises, in the Promises/A+ sense, is that they capture thrown exceptions so they can be handled asynchronously. This has the negative consequence that it is possible for an error to be silently ignored forever. There is a long list of measures that can mitigate this problem, but the real solution is to use a Promise Inspector.

The JavaScript console is not satisfying. The trouble with a rejected promise is that it might not be later connected to a promise chain that would handle recover from or surface the issue. The naïve solution would be to log every exception to the console, but then it becomes difficult to distinguish errors that have been handled programmatically from errors that never will be. Ideally, the promise inspector would show rejected promises until they are handled and then hide them. That way, if a program reaches an obviously stable or idle state and a rejection remains on the console, it is very likely due to a programmer’s error.

There is a less obvious class of problems a promise inspector can surface: the forever pending promise. Consider a promise inspector that shows a pending promise until it is either fulfilled or rejected. If a program reaches an obviously idle or stable state and a pending promise remains in the inspector, it is the harbinger of a problem, and the best place to start inspecting.

There is precedent for promise inspectors. The most noteworthy is Causeway, a promise debugger designed for Mark Miller’s E programming language. Ember Extension has recently introduced a promise inspector. There is also a proof-of-concept for a Chrome Extension that introduces a Promise inspector for the Q promise library.

It would also be useful to look into CapTP and CapNProto for prior art.

Promises come and go over the life of a program. As such, it is not practical to retain the entire history of promises in the memory of either a promise inspector or a promise library. It is however practical for a library to retain an index of all pending promises and all promises that have been rejected but not yet handled. It would also be practical for a promise inspector to retain a circular buffer of a limited size for all other promises for the purpose of tracing.

The scope and life of a promise inspector panel and inspected windows overlap but are disjoint. Ideally, there would be one promise inspector panel for each window under test and their lives would be coordinated, but this is not the case for multiple reasons. Reloading the inspected window does not cause the inspector window to reload. The inspector window can be opened and closed multiple times during the lifetime of a single inspected window, open or closed when the inspected page loads, or open or closed when the inspected page unloads. Furthermore, an inspected page does not necessarily only contain one instance of a promise library, and an inspector window does not necessarily need to limit itself to watching a particular page.

It is not merely conceivable that a promise inspector could watch promises from multiple windows, processes, or instances of various promise libraries, but Causeway is explicitly designed to show the causal relationships between promises in programs distributed over a network.

This implies some basic requirements for a promise inspector protocol for Chrome Extensions.

  1. A promise library must be able to communicate with any number of inspectors simultaneously.
  2. A promise inspector must be able to communicate with any number of promise libraries, across any number of windows, across any number of processes simultaneously. But for now, just one that will come and go at random, and may or may not be already there when the inspector window loads.
  3. Individual promises must have universally unique identifiers. This might imply a crytorandom identifier, or just a sequential number attached to a cryptorandom promise library instance identifier.
  4. A promise library must retain a snapshot of all currently pending and rejected but unhandled promises and be able to transmit this snapshot and all subsequent changes each time it connects or reconnects to a promise inspector window.
  5. A promise inspector must be able to display stack traces corresponding to where a promise was constructed, their state (pending, fulfilled, rejected, thenable, remote), and their resolution if they are initially pending. The inspector is at liberty to use this information for a variety of visualizations.

Also, the message transport mechanism between an inspected window and an inspector window, with the agents coming and going, is roughly as reliable as any shared network. It might become necessary to construct something like TCP/IP over message ports so multiple objects within multiple contexts can reliably communicate over bidirectional streams.

@daira
Copy link

daira commented Feb 12, 2014

From my experience with Deferreds in Python, the most important things to know about a dropped deferred are the stack traces of where it was created, and where it was fired (i.e. fulfilled or rejected). Twisted unfortunately doesn't keep that information by default which is a huge pain.

@kriskowal
Copy link
Author

@daira The promise library can certainly retain a WeakMap with a sequence number. The promise inspector does not exist in the same process. The library would have to broadcast its unique identifier with the initial snapshot, and use sequence numbers to identify promise instances, as those messages would all be marshaled.

I also have some experience with Twisted. Q does not retain old promises either, at the moment, but it would be straightforward. Q already has some code for unhandled rejection tracking.

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