ES6 Promises

At first there were callbacks...

function doSomething(callback) {
  let value = 42;
  // some more code ....
  callback(value);
}

doSomething(function(value) {
  console.log('Got a value:' + value);
});

And events ...

let img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // woo yey image loaded
});

img1.addEventListener('error', function() {
  // argh everything's broken
});

What happens if we hook the event handler after the event already happened?

So what is a promise?

A Promise is a container for an asynchronously delivered value. It's like a special box that preforms a task, this box is promised to contain the result sometime in the future

let p = new Promise(function(resolve, reject) {  
   if (/* condition */) {
      resolve(/* value */);  // fulfilled successfully
   }
   else {
      reject(/* reason */);  // error, rejected
   }
});

Getting results

// preform a task (often async task)
let p = new Promise((resolve, reject) => {  
    // typeof(resolve) === "function"
    // typeof(reject) === "function"

    // when task completes, call resolve()
    setTimeout(() => resolve("some value"), 2000);

    // if task fails, call reject or throw an error
});


// get the result
p.then(res => console.log(res)) // "some value"


// NOTE: then doesn't run the task, only gets the value
p.then(res => console.log(res)) // still "some value"

Demo!

Catching errors

// preform a task (often async task)
let p = new Promise((resolve, reject) => {  

    // when task completes, call resolve()
    setTimeout(() => resolve("some value"), 2000);

    // if task fails, call reject or throw an error
});

// get the result 
p.then(res => console.log(res)) // "some value"
p.catch(err => console.log(err)) // catch an error

// or 
p
.then(res => console.log(res)) 
.catch(err => console.log(err)) 

Anatomy of a promise

func(resolve, reject) {
...
}

resolved value

rejected value

.then()

.catch()

fulfilled (resolve was called)

rejected

(reject was called or error was thrown)

Promise states

  • Pending - still working to the promise func
  • Fulfilled (resolved)
  • Rejected (or error was thrown)

Calling then - sync or async ?

  • If the promise is fulfilled - we get the result synchronously
  • If the promise is pending - our callback will be called asynchronously when the promise is resolved
  • if the promise is rejected - our callback won't be called

What errors can we catch() ?

  1. A call to reject() inside the promise
  2. Thrown errors inside the promise
  3. Thrown errors inside a then()
let p = new Promise(...);

p.then((res) => {
    console.log(res)
})
.catch(err => {
    console.log(err)
})

Demo

Passing a promise

You can pass the promise around, as a parameter or return it in a synchronous fashion, just like every other value

function saveData(data) {
    return new Promise((resolve, reject) => {
        // do the work when finished, call resolve
        // if an error occured, call reject
    })
}


// use it like any other value, get notified when the promise is fulfiled
saveData(someDataObject).then((res) => {
    console.log(res)
})

Consuming a promise

let p = new Promise((resolve, reject) => { ... });

p.then(val => console.log("fulfilled:", val))  
 .catch(err => console.log("rejected:", err));


// piping
p.then(val => val * 2)  
 .then(val => console.log(val)) // returned value of then is rewraped as a promise
 .then(val => throw new Error()) // throwing an error in "then" will be handled by the catch
 .catch(err => console.log("rejected:", err));

Using fetch()

// fetch returns a promise
fetch('some url')
  .then(res => {
    if(res.ok) {
      return res.json(); // returned value is rewraped as a promise for the next then()
    }

    // this will be caught by the catch() func
    throw new Error('Network response was not ok.');
  })
  .then(json => { 
    console.log(json)
  })
  .catch(err => {
    console.log('There has been a problem with your fetch operation: ' + err.message);
  });

Promise.resolve()

function getData(offline) {
    if (offline) {
        return Promise.resolve([])        
    }
    else {
        // ES6 fetch returns a promise natively
        return fetch("https://jsonplaceholder.typicode.com/posts")
    }    
}

// consumed as a promise even if nothing async ever happens
getData(false)
    .then(data => console.log(data)
    .catch(err => console.log(err))


// same for Promise.reject(reason)

Promise.all([])

let itemUrls = [
    'https://jsonplaceholder.typicode.com/posts',
    'https://jsonplaceholder.typicode.com/users'
  ];

itemPromises = itemUrls.map(url => fetch(url));

Promise.all(itemPromises)  
  .then(promises => 
    // we only get here if ALL promises fulfill
    promises.forEach(fetchPromise => { 
        fetchPromise.json().then(data => console.log(data)) 
    }) 
  )

Nesting Promise.all

let urls = [...]

let itemPromises = urls.map(url => fetch(url));

Promise.all(itemPromises)
    .then(results => {
        Promise.all(results.map(res => res.json())).then(data => console.log(data))
    })

Promise.race([])

let p1 = new Promise((resolve, reject) => { 
    setTimeout(resolve, 500, 'one'); 
});
let p2 = new Promise((resolve, reject) => { 
    setTimeout(resolve, 100, 'two'); 
});

Promise.race([p1, p2]).then(value => {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

Promise methods

Promise.resolve(value)

Promise.reject(reason)

Promise.all(iterable)

Promise.race(iterable)

 

Promise.prototype.then(callback)

Promise.prototype.catch(callback)

Summary

Promises give us the ability to write asynchronous code in a synchronous fashion, with flat indentation and a single exception channel.

ES6 Promises

By Gabi Mor

ES6 Promises

  • 608