Promises are cool. The best source for Promise API basics is still the API documentation on MDN. Well here’s some news. Promises are awesome and really easy to work with. That article will teach you all about the built in methods, like “all” and “resolve”, we’re going to go over some useful techniques that are not built into the Promise object.
First, let’s do a simple promise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Create a function to pass the new Promise instance. // Resolve and reject are how the promise figures out what to do when it's done. // // This is a simple time out. var promisable0 = function(resolve, reject){ setTimeout(function(){ resolve('Hello'); }, 2000); }; //Set up the promise, by creating a new instance of Promise passing in our promisable function. var basicPromise = new Promise(promisable0); // Create a function we want to execute when that is done. // The parameter is the thing you resolved. function myFirstThen(result){ console.log(result); } // Now let's use it! basicPromise.then(myFirstThen); |
What if we want to do something sequentially? Promise.all() waits for all of the promised functions, Promise.race waits for one… but a lot of times, we want to run sequences without losing the benefits of asynchronous code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var count = 0; // This example's promisable is the same (with just a different string) var promisable = function(resolve, reject){ setTimeout(function(){ resolve('Working'); }, 2000); }; var firstPromise = new Promise(promisable); // A more complex thenable, returns another promise, each then method makes // sure the previous then isn't returning a promise. // Here, it is, so it waits. If it's not waiting for a Promise to be // resolved, these all execute at once. function thenable(result){ console.log('| ', count, ' - ', result); count++ return new Promise(promisable); }; firstPromise.then(thenable).then(thenable).then(thenable).then(thenable); |
Next, let’s make it a bit more complex, with issuing the then in the then. Adding the reseting the counter and using our thenableLoop.
| 0 – Working
| 1 – Working
| 2 – Working
| 3 – Working
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
... function resetable(result){ count = 0; return new Promise(promisable); } function thenableLoop(result){ // using * character to have a visible difference in output. console.log('* ', count, ' - ', result); count++ if(count < 6) { // here we return a promise and issue the next then to keep the loop going. return new Promise(promisable).then(thenableloop); } else { console.log('Done'); } }; // firstPromise.then(thenable).then(thenable).then(resetable).then(thenableLoop); |
This outputs like so:
| 0 – Working
| 1 – Working
* 0 – Working
* 1 – Working
* 2 – Working
* 3 – Working
* 4 – Working
* 5 – Working
Done
You can also pass callbacks as promisables…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Changing the string again in the resolve method in our Promise, // for following along. var promisable2 = function(resolve, reject){ setTimeout(function(){ resolve('Still Working'); }, 2000); }; var secondPromise = new Promise(promisable2); function callbackable(callback, result){ console.log('| ', count, ' - ', result); count++; return new Promise(callback); } // This one binds the callback in the first argument. Leave the // context (this) undefined, or pass it a context! // once it is there, it goes to thenable, which uses our new string // that is the result of the promise passed in. secondPromise.then(callbackable.bind(undefined, promisable2)).then(thenable); |
That outputs…
| 0 – Still Working
| 1 – Still Working
Keep in mind if we passed in promisable, instead of promisable 2 it would look like this:
| 0 – Still Working
| 1 – Working
Finally, let’s look at binding arguments to the promise…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Put the arg first, just like the call back in the previous example... var arguablePromisable = function(arg, resolve, reject){ setTimeout(function(){ resolve(arg); }, 2000); }; // Here, we bind it as we pass it var thirdPromise = new Promise(arguablePromisable.bind(undefined, 'Custom String')); // // or before hand... // var promiseWithArgs = arguablePromisable.bind(undefined, 'Custom String'); // var thirdPromise = new Promise(promiseWithArgs); // using thenable and callbackable from the previous example. thirdPromise.then(callbackable.bind(undefined, arguablePromisable.bind(undefined, 'Custom String'))).then(thenable); |
This outputs:
| 0 – Custom String
| 1 – Custom String
You can have very powerful sequences, with non-blocking IO. This is great for real world applications using ES2015/ES6. You could easily replace our timeouts in these examples with AJAX calls, queries to a datastore, complex DOM interactions, or anything else you might want to ensure is done before executing another set of instructions.
Update: Guatom makes a good point (in the comments below), we’re all more familiar with ES2015 concepts, you can avoid using “bind” by using arrow functions, which keep the context of where the function was called. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions