Let's back up a bit and describe what the Promise constructor does. The Promise constructor is passed a callback that is called synchronously. The idea behind this callback (which is often called the "Promise executor callback") is that it will initiate some asynchronous operation and then immediately return. Some time later when that asynchronous operation is done, it will then resolve or reject that promise.
Here's a simple example that we can go through step by step (you can run this example in the snippet below to see the logging sequence):
console.log("0");
let p = new Promise(function(resolve, reject) {
console.log("1");
setTimeout(function() {
console.log("2");
resolve("done");
}, 1000);
}).then(function(val) {
console.log("3");
});
console.log("4");
You will get this output:
0 // start of the code
1 // Promise executor callback called and setTimeout() initiated
4 // Promise created and now initialized
2 // setTimeout fires
3 // Promise gets resolved and .then() handler called
And, here are the sequence of events that happen:
console.log("0");
runs
- You call
new Promise()
and pass it a callback function
- The Promise constructor runs, creates a promise object and synchronously calls the callback function
- The purpose of the callback function is to initiate some non-blocking, asynchronous operation (in this case a simple
setTimeout()
), but it could be a much more complex operation such as doing some file operation, searching a database, etc...
- The callback function is passed two arguments, each of which is a function. The callback should call one of those two functions when the asynchronous operation that was initiated in the callback completes. If this is a typical asynchronous operation, the callback will start the asynchronous operation and then return. Then sometime later a different callback associated with the asynchronous operation will get called and depending upon whether the operation was successful or returned an error, that callback will then call one of the two functions that were passed to the callback (which I named
resolve
and reject
in my example. Your example picks much longer names.
- In my code example, the Promise callback (called the Promise executor), immediately logs to the console and then calls
setTimeout()
. Because setTimeout()
is non-blocking and asynchronous, it starts the timer and immediately returns. At this point, the Promise executor callback is done and returns.
- The timer is now running, the promise executor callback has run and has returned. The promise constructor is done and it returns the new Promise object.
- Then, the
.then()
method on the promise is called. That registers a fulfill callback for the promise and then immediately returns. Technically, it returns a second promise that is chained to the first one that was created with the Promise constructor. This .then()
callback that was registered is stored away, but not yet called.
- Now the last line of code in this example executes and it logs
4
to the console.
- This code is done (for now). Any other code that follows this would execute now or if there is no other code, then the Javascript interpreter goes back to the event loop and waits for the next event to occur.
- Sometime later, the timer fires. It inserts an event in the Javascript event queue and when the interpreter is done doing anything else it was doing and fetches the next event from the event queue, it will call the callback that was passed to
setTimeout()
.
- When that
setTimeout()
callback executes, it will log 2
and then call resolve("done")
. Calling resolve()
resolves the first promise and tells it to then trigger any .then()
handlers and call them.
- At this point, the callback passed to
.then()
is called and it logs 3
and everything is done.
Now to answer some of the specific questions about your code example:
What I don't understand is: when is getAPhone called? I didn't call it. Who called it? When? How?
You passed getAPhone
to the Promise constructor. The Promise constructor calls it as part of running the constructor to create the new Promise object.
Here's a simple example of how that happens:
function doIt(someCallback) {
someCallback("hi");
}
function myCallback(greeting) {
console.log(greeting);
}
doIt(myCallback); // logs "greeting"
When you call doIt, you pass it a function reference (essentially a pointer to a function). This allows the function you're calling to call the function with whatever arguments it wants to when it wants to call it. In this case, doIt()
expects you to pass it one function reference and it will immediately call that function and pass it "hi"
.
So why doesn't the promise constructor just block my code when it runs my function? If all the constructor is doing is immediately calling a function for me, why is it different from just calling the function directly?
The promise constructor is run synchronously (when you call new Promise()
). The promise executor callback function is run synchronously by the Promise constructor. But, what it typically does is just initiate an asynchronous and non-blocking operation and then immediately returns. The asynchronous operation then runs in the background and will trigger it's own callback sometime in the future when it completes.
So then why doesn't the code block inside the Promise's constructor? Isn't it a function on the call stack like everything else?
The Promise constructor is blocking. It doesn't return until it finishes. It is a function on the call stack like everything else.
What may be confusing you here is that your getAPhone()
example does not contain any asynchronous code. It's entirely synchronous. There is NO reason to use promises with that code at all.
Here's a little less obtuse version of that function:
var momIsHappy = false;
var getAPhone = function (resolve, reject) {
if (momIsHappy) {
var phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone); // fulfilled
} else {
var reason = new Error('mom is not happy');
reject(reason); // reject
}
}
All this does is examine momIsHappy
and then immediately resolve or reject the promise. There is no asynchronous operation here. There is no reason to use promises with your example at all. Promises are tools for managing and coordinating asynchronous operations. You should not use them if you don't have any asynchronous operations because they add complexity to synchronous code when plain and simpler function calls can be used instead. With asynchronous operations, it can be difficult to coordinate and manage asynchronous operations and error handling, particularly when there are multiple asynchronous operations that need to be sequenced or coordinated and all errors handled. That's what promises are designed for.
So, in your specific case, you could have just made getAPhone()
into a regular function, just called it directly and not used promises at all.