Javascript Promises – Part I – Getting started
Have you ever seen those javascript codes that keep adding .then()
and .then()
all over the place and you wonder what the hell is happenning? 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 we tell the code to do some task, we don’t wait for it to finish, but instead, we pass a callback to it so that the code can call the callback when the task is done.
Now if you want the callback above to do another 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 Promise is the Promise constructor function, which looks 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 });
We can 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 parameters including a resolve
function – which main will call when the job is successfully done; and a reject
function – which main will call if there is any error while executing the job.
Let’s see the whole thing in action to better understand it.
Introduction to Promise
This is how a code with Promise might look like:
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 returns a Promise
. The Promise
loads an image; and then if the loading succeeds, calls resolve
with url
as parameter and if failed, calls reject
, also with url
as parameter.
It’s normal if you don’t get the idea up until now. We’ll try to explain more in details in the several next paragraphs.
Now we can call this function like below:
getImage('doggy.jpg').then(function(successurl){ console.log('successfully loaded: ' + successurl); });
Let’s first understand what’ve just happenned.
After getImage return a Promise
, we provide 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 we have rough idea of what a Promise looks like, let’s take a look at a simpler example.
The easiest way to get a visualization on this is by opening a Google Chrome browser and open Developer Tools
panel (usually by pressing F12
on Windows, or Command-Alt-I
on Mac, or from the Menu -> More tools -> Developer tools
). Navigate to console tab and let’s do 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
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 results in something like this in the console:
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;
Notice that this time we’ve changed return 'value 1';
in the previous code block to resolve('value 1');
. The console now returns 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 returns 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
and return the Promise
instead. Later, 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 as below:
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.
Next in series: Javascript Promises – Part II – Handling errors