Javascript async/await patterns

by Chris Hiestand @dimmer

Unhandled Rejection Anti-Example


import _Fetch from 'isomorphic-fetch';

// We intentionally are *not* running an http server on port 1337
function fetch_P() {

    return _Fetch('http://localhost:1337/servernotrunning.json')
    .then((result) => {

        return result.json();
    });
}

async function main() {

    let data = await fetch_P();
    console.log('got data');
}

// This will fail silently
main();

Output



    

Unhandled Rejection Workaround


import _Fetch from 'isomorphic-fetch';

// workaround for hidden promise rejections, see:
// <https://github.com/nodejs/promises/issues/26>
process.on('unhandledRejection', err => { throw err; });

// We intentionally are *not* running an http server on port 1337
function fetch_P() {

    return _Fetch('http://localhost:1337/servernotrunning.json')
    .then((result) => {

        return result.json();
    });
}

async function main() {

    let data = await fetch_P();
    console.log('got data');
}

// This will fail but will at least an error will be thrown
main();

Output


/path/node_modules/core-js/library/modules/es6.promise.js:125
    if(abrupt)throw abrupt.error;
              ^

Error: request to http://localhost:1337/servernotrunning.json failed, reason: connect ECONNREFUSED 127.0.0.1:1337
    at ClientRequest.<anonymous> (/path/node_modules/node-fetch/index.js:117:11)
    at emitOne (events.js:77:13)
    at ClientRequest.emit (events.js:169:7)
    at Socket.socketErrorListener (_http_client.js:256:9)
    at emitOne (events.js:77:13)
    at Socket.emit (events.js:169:7)
    at emitErrorNT (net.js:1255:8)
    at nextTickCallbackWith2Args (node.js:437:9)
    at process._tickCallback (node.js:351:17)

    

Async with Promises


function main() {

    // Initializing an empty variable to ensure
    // lexical/thenable scope always felt awkward
    let data;

    // fetch_P() is called first
    fetch_P()
    .then((result) => {

        // This happens third
        data = result;
        console.log('got data');
    });

    // This happens second
    console.log("shouldn't I have the data here?
    I mean, this code *is* further down the screen.");
}

Async with async/await


async function main() {

    // This happens first
    let data = await fetch_P();

    // This happens second
    console.log('got data');

    // This happens third
    console.log('sanity restored');
}

Inefficient promise anti-example


// fetch_P returns a promise
async function fetch_P(auth, key) {

    // anti-pattern: an unnecessary additional
    // remote call in a function
    let config = await getConfig_P(auth);

    return getValue_P(config, key);
}

async function main(auth) {
    let value1 = fetch_P(auth, 'key1'); // gets config
    let value2 = fetch_P(auth, 'key2'); // gets config too
    let value3 = fetch_P(auth, 'key3'); // gets config three
}

minimum promises per function example


function fetch_P(config, key) {

    // Performs a single remote operation
    return getValue_P(config, key);
}

async function main(auth) {

    let config = await getConfig_P(auth);

    // These promises execute in parallel
    let p1 = fetch_P(config, 'key1');
    let p2 = fetch_P(config, 'key2');
    let p3 = fetch_P(config, 'key3');

    // This only executes after all 3
    // parallel promises are settled
    // assignment is stable: order is retained
    let [value1, value2, value3] = await Promise.all([p1, p2, p3]);
}

Javascript can sleep


import Assert from 'assert';
import _Promise from 'bluebird';

async function test() {

    decoupledRemoteThing();

    // decoupledRemoteThing should be complete after 1 second
    // Akin to `sleep(1000)` in synchronous languages
    await _Promise.delay(1000);

    // This runs at least 1 second after
    // decoupledRemoteThing() was executed
    let remoteValue = await getRemoteValue_P();
    Assert(remoteValue === true);
}

Background async requests and defer waiting


async function main() {

    let inputQueue = await getIntputQueue_P();

    // Start this operation now, but we will use
    // the result later; note the lack of `await`
    let outputQueueDefer = getOutputQueue_P();

    // This may take some time
    let workDone = await doWork_P(inputQueue);

    // By the time doWork_P() is done, this promise
    // is probably already resolved
    let outputQueue = await outputQueueDefer;

    await putResult_P(outputQueue, workDone);
}

Summary

  • Return promises (or use async functions) everywhere you do non-streaming asynchronous calls
  • Don't forget process.on('unhandledRejection', ...)
  • Minimize the number of remote calls per function.
  • For performance, start a promise but defer the await until other work is done.
  • This line brings order from chaos:
    let [value1, value2, value3] = await Promise.all([p1, p2, p3])
  • sleep(x)await _Promise.delay(x)

Blog post at: http://goo.gl/rG2Tb5