Skip to content

Promises

Classical, event based

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var img1 = document.querySelector('.img-1');

function loaded() {
    // woo yey image loaded
}

if (img1.complete) {
    loaded();
} else {
    img1.addEventListener('load', loaded);
}

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

Promise way

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
img1.ready().then(function() {
    // loaded
}, function() {
    // failed
});

// and ...
Promise.all([img1.ready(), img2.ready()]).then(function() {
    // all loaded
}), function() {
    // one or more failed
});

Promise lifecycle

Promise lifecycle - pending, fulfille, rejected

Promise lifecycle - fullfillment/rejection

  • A promise can be:
    • fulfilled - The action relating to the promise succeeded
    • rejected - The action relating to the promise failed
    • pending - Hasn't fulfilled or rejected yet
    • settled - Has fulfilled or rejected


Create your own

1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
    // do a thing, possibly async, then ...

    if (/* everything returned out fine */) {
        resolve('Stuff worked');
    } else {
        reject(new Error('It broke'));
    }
});
1
2
3
4
5
promise.then(function(result) {
    console.log(result);    // "Stuff worked"
}, function(err) {
    console.log(err);       // Error: "It broke"
});

In practice, it is often desirable to catch rejected promises rather than use then's two case syntax.

Fail and success of a promise

A promise can only succeed or fail once. It cannot succeed or fail twice, neither can it switch from success to failure or vice versa. If a promise has succeeded or failed and you later add a success/failure callback, the correct callback will be called, even though the event took place earlier.

Handling result

The promise fate can be subscribed to using .then (if resolved) or .catch (if rejected).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const promise = new Promise((resolve, reject) => {
    resolve(123);
});

promise.then((res) => {
    console.log('I get called:', res === 123);  // I get called: true
});

promise.catch((err) => {
    // This is never claled
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const promise = new Promise((resolve, reject) => {
    reject(new Error("Something awful happened"));
});

promise.then((res) => {
    // This is never called
});

promise.catch((err) => {
    console.log('I get called: ', err.message); // I get called: Something awful happened
});

Quickly creating an already resolved promise: Promise.resolve(result) Quickly creating an already rejected promise: Promise.reject(error)

Chaining

  • Chaining - Queueing asynchronous actions
  • When you return something from a then() callback, it's a bit magic.
    • If you return a value, the next then() is called with that value.
    • If you return something promise-like, the next then() waits on it, and is only called when that promise settles (succeeds/fails)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var promise = new Promise(function(resolve, reject) {
    resolve(1);
});

promise.then(function(val) {
    console.log(val);   // 1
    return val + 2;
}).then(function(val) {
    console.log(val);   // 3
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Promise.resolve(123)
    .then((res) => {
        console.log(res);   // 123
        return 456;
    })
    .then((res) => {
        console.log(res);   // 456
        return Promise.resolve(123);    // Notice that we are returning a Promise
    })
    .then((res) => {
        console.log(res);   // 123 : Notice that this 'then' is called when resolved
        return 123;
    });

Error handling

  • Aggregate the error handling of any preceding portion of the chain with a single catch
  • The catch actually returns a new promise (effectively creating a new promise chain)
  • Any synchronous errors thrown in a then (or catch) result in the returned promise to fail
  • Only the relevant (nearest tailing) catch is called for a given error (as the catch starts a new promise chain)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Promise.reject(new Error('Something bad happened'))
    .then((res) => {
        console.log(res);   // 123
        return 456;
    })
    .then((res) => {
        console.log(res);   // 456
        return Promise.resolve(123);    // Notice that we are returning a Promise
    })
    .then((res) => {
        console.log(res);   // 123 : Notice that this 'then' is called when resolved
        return 123;
    })
    .catch((err) => {
        console.log(err);   // Something bad happened
    });

Parrallel control flow

  • Promise provides a static Promise.all function that you can use to wait for N number of promises to complete.
  • Promise provides a static Promise.racefunction that can be used to wait for the first of N promises to complete

Async

  • Async functions - making promises more friendly
  • They allow you to write promise-based code as if it were synchronous, but without blocking the main thread. They make your asynchronous code less "clever" and more readable.
  • Use the async keyword before a function definition, then use await within the function. When you await a promise, the function is paused in a non-blocking way until the promise settles. If the promise fulfills, you get the value back. If the promise rejects, the rejected value is thrown.
1
2
3
4
5
6
7
async function myFirstAsyncFunction() {
    try {
        const fulFilledValue = await promise;
    } catch (rejectedValue) {
        // ...
    }
}

Comparison

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function logFetch(url) {
    return fetch(url)
        .then(response => response.text())
        .then(text => {
            console.log(text);
        })
        .catch(err => {
            console.error('fetch failed', err);
        })
}
1
2
3
4
5
6
7
8
async function logFetch(urL) {
    try {
        const response = await fetch(url);
        console.log(await response.text());
    } catch (err) {
        console.error('fetch failed', err);
    }
}