Have you ever seen those javascript codes that keep adding .then() and .then() all over the place and you wonder what the hell is it with those .then()? You are not alone my friend. Today I'm going to talk about these things called Promise and tell you what, after you understand this Promise thing, you'll find it one of the coolest thing Javascript has ever made.
Who should read this article?
This article applies to both javascript on the browser and javascript in Node.JS. So anyone who uses either of those may find this article helpful.
Why Promise?
Back in history, Javascript is all about callbacks. When you tell the code to do some task, you create a callback so that the code can call it when the task is done.
Now if you want the callback above to do some task and then do something with the result from this second task, you add another callback to the previous callback. And when this adds up, you may get 10 layers of callback in your code and now you can't even understand your own code flow. This situation is usually referred to as "callback hell".
Getting started
Ok, let's get down to business. At the heart of JavaScript Promises is the Promise constructor function, which is called like this:
var mypromise = new Promise(function(resolve, reject){
// asynchronous code to run here
// call resolve() to indicate task successfully completed
// call reject() to indicate task has failed
})
Let's think of it this way: each Promise has a "main" function, which is the code that actually does the job. This "main" function takes two parameter as resolve function, which main will call when the job is successfully done, and reject function, which main will call if there is any error while executing the job.
Let's see this in action to better understand it.
The easiest way to do this is open a chrome browser and open developer panel (usually by pressing F12 or from the Menu -> More tools -> Developer tools). Navigate to console tab and let's start the business.
Ok you don't need incognito mode. I just open incognito mode out of habit, if you know what I mean ;))
Creating your first Promise
function getImage(url){
return new Promise(function(resolve, reject){
var img = new Image()
img.onload = function(){
resolve(url)
}
img.onerror = function(){
reject(url)
}
img.src = url
})
}
What just happened? We've just created a function which return a Promise. The Promise load an image and then if succeeds, call resolve with url as parameter and if failed, call reject with url as parameter.
Now let's call this function
getImage('doggy.jpg').then(function(successurl){
console.log('successfully loaded: ' + successurl);
})
Ok this code won't work in your browser but let's first understand what happens here.
After getImage return a Promise, we bind that Promise with a resolve function. The javascript engine will then pass the url to the resolve function as stated in the main function of the Promise:
img.onload = function(){
resolve(url)
}
Now that you get the rough idea of what a Promise is, let's take a look at a simpler example.
Paste this in your chrome's console:
var p1 = new Promise(function(resolve, reject) {
console.log('1');
return 'value 1';
});
p1;
We've just create a Promise named p1 and then print it to console to see its status, which is something like this:
Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
So p1's status is pending, and no PromiseValue is returned yet. That is because we haven't called resolve nor reject while executing the main function
Let's change p1 a little bit
var p1 = new Promise(function(resolve, reject) {
console.log('1');
resolve('value 1');
});
p1;
The console returned with:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "value 1"}
Yay! The promise is now resolved, and PromiseValue is now "value 1".
Now let's type this into your console:
var p2 = p1.then(function(val) {
console.log('2: ' + val);
return val;
});
p2;
The console returned with:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "value 1"}
What just happened? By calling p1.then, we specify the resolve function for p1. So now when the main function of p1 compeletes, it knows which resolve function to call. So now it call the function specified in then(), and pass p1.PromiseValue to that function as param (in this case val).
But wait, p1 already finished before the real resolve function was passed to p1.then. How did that resolve function be called?
So, Promise is a mechanism provided by javascript engine, and Javascript Engine is the one who called that resolve function. Let's imagine that Javascript Engine has a timer that continuously check the status of Promise p1. When p1 finishes, it updates the p1.status to either resolved or rejected, and save the PromiseValue so that it will use to pass to resolve or reject function later on. Then it checks if a real resolve function is specified. If no resolve function is specified, it just leave the Promise there and recheck in its next timer loop. Half an hour later, someone specifies the real resolve function for p1 by calling p1.then(resolveFunc). In its next timer loop, Javascript Engine finds out that p1 now has a real resolve function so Javascript Engine calls that function with p1.PromiseValue as the function's first parameter.
Another fancy thing to notice in the previous example is that p2 is also a Promise. Technically, to return a Promise, that block of code should be rewritten as below:
var p2 = p1.then(function(val) {
console.log('2: ' + val);
return new Promise(function (resolve, reject) {
resolve(val);
})
});
By default, Promise.then() returns a Promise. But Promise.then() is smart enough to check if the passed in function returns a value, it will wrap that function into a Promise and the returned value of that function will be the PromiseValue. On the other hand, if the passed in function returns a Promise, Promise.then() will just forward that Promise as its returned object.
Therefore, in the previous block of code, when .then() finds out that the passed in function just return val; it wrap that function into a Promise to return, and when that function finishes, it knows that it would use the value returned from the function to assign to PromiseValue of the returned Promise (p2).
Now that p2 is a promise, we can continue to use then() on it.
var p3 = p2.then(function(val) {
console.log('3: ' + val);
return val;
});
p3;
The console output should be like this:
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "value 1"}
which means p3 is also a Promise, it has been resolved and its PromiseValue is "value 1", waiting to be passed on to the next Promise if there is any.
Ok that's it for part I. Now you know what Promise is and what those .then() functions mean.
In the next parts, we will look more into error handling in Promise and how Promise's are chained.