JavaScript Concurrency: What's Inside a Promise?
Welcome to the What's Inside a Promise? lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
In an earlier lesson, we saw that Execute Program represents promises like
{fulfilled: someValue}. This raises a subtle but important question: is that what promises "really" look like inside? No; that's just how Execute Program shows them to you!The promises used in this course are built in to your browser, so we can't directly look at their internal state. However, we do have a way around that. We can ignore the browser's promises for a moment, switching to a promise library that's implemented in pure JavaScript.
The most popular promise library is bluebird, which supports the same promise API that we've seen in this course. By looking at the internals of a Bluebird promise, we can get a sense of what's happening under the hood. Here's what's really inside a bluebird promise:
>
bluebird.Promise.resolve(5);Result:
Now you can see why we ask you to type
{fulfilled: 5}instead of typing out the "real" promise object! In browsers, there's no "real" promise object to type because promises' internals are invisible to JavaScript. And pure-JavaScript promise libraries like Bluebird have too much complex internal state to type out manually.The question of "what's inside a promise?" can be confusing when bugs show up in promise code. For example, what happens if we accidentally serialize a promise into JSON?
If it's a bluebird promise, we'll get the JSON version of the large object shown above. But if it's the browser's built-in type of promise, we'll get
{}because its internals are hidden from us!>
JSON.stringify(Promise.resolve(5));Result:
If we "round-trip" the promise back into a JavaScript object with
JSON.parse, it will still be{}(with no quotes).>
JSON.parse(JSON.stringify(Promise.resolve(5)));Result:
{}Returning a promise instead of its value can be the source of very confusing bugs. Imagine that we have a system with two parts: an API server that returns JSON responses and a client that consumes them. If we want our API server to return the value inside a promise, the correct solution is to use
thento access the value inside the promise.Suppose that we accidentally add a bug: we return the promise itself, so the promise gets serialized into JSON. The client will see
{}, like in the example above.The
{}will make us think that we accidentally returned an empty object. In reality, we accidentally returned a promise. That promise may have contained the correct data in its fulfilled value, but the client will still see{}!We can get this kind of bug even without involving JSON. For example, it will happen with
Object.assign, which merges one object's properties into another object.>
Object.assign({name: 'Betty'}, {city: 'Nassau'});Result:
>
const bettyPromise = Promise.resolve({name: 'Betty', city: 'Nassau'});Object.assign({}, bettyPromise);Result:
You may be asking yourself why promises seem to be especially awkward in this way. But this problem isn't unique to promises. The JSON and
Object.assignproblems also happen withErrorinstances and many other types of JavaScript objects.>
JSON.parse(JSON.stringify(new Error('it failed')));Result:
>
Object.assign({}, new Error('it failed'));Result:
This is a dangerous corner of JavaScript that we have to learn to work around. But knowing about this can save a lot of debugging time when you encounter it!