@rob_rich

async & await with Node and Express

by Rob Richardson

@rob_rich

https://robrich.org/

About Me

Rob Richardson is a software craftsman building web properties in ASP.NET and Node, Angular and Vue. He's a frequent speaker at conferences, user groups, and community events, and a diligent teacher and student of high quality software development. You can find this and other talks on his blog at https://robrich.org/presentations and follow him on twitter at @rob_rich.

Can I use async & await?

I can use Promises and async await now source: https://node.green/

Why Async

Do things in parallel

MDI source: http://benhoff.net/qt-interface-design.html

Yield while I'm waiting

shared hosting source: https://itxdesign.com/vps-vs-shared-hosting/

Race Conditions

Race Conditions source: https://hacks.mozilla.org/2017/06/avoiding-race-conditions-in-sharedarraybuffers-with-atomics/

Node Event Loop

Race Conditions source: https://www.youtube.com/watch?v=S5XEU3XVMdo

Async Techniques

Callbacks

In the beginning ...

callLib(inp, args, function (err, res, ults) {
  if (err) {
    // handle error
    return;
  }
  // handle success
});

Callbacks

hard The nested wing

callLib(req, function (err, res1) {
    if (err) {
        return handle(err);
    }
    callLib(function (err, res2) {
        if (err) {
            return handle(err);
        }
        callLib(function (err, res3) {
            if (err) {
                return handle(err);
            }
            callLib(function (err, res4) {
                if (err) {
                    return handle(err);
                }
                done(res1, res2, res3, res4);
            });
        });
    });
});

Callbacks

easy Pros

  • Simple
  • Supported everywhere

hard Cons

  • Indent off the page
  • Can't throw
  • Optional parameters are hard
  • Parallel execution is hard

Promises

Promises

callLib1(inp, args)
  .then(r => callLib2(r))
  .then(done)
  .catch(handleError);
function callLib1(inp, args) {
  // all evergreen browsers, since Node 6
  return new Promise(function (resolve, reject) {
    // do stuff
    if (err) {
      return reject(err);
    }
    resolve(answer);
  });
}

Promises

easy return wraps in promise

callLib1(inp, args)
  .then(function (r1) {
    return new Promise((resolve, reject) => {
      resolve(r1);
    });
  })
  .then(function (r2) {
    return r2;
  })
  .then(done)
  .catch(handleError);

Promises

easy Pros

  • Reduced nested wing
  • return and throw auto wrapped in promise
  • Parallel execution
  • Single result so built-in caching

hard Cons

  • Then-a-thon

async & await

async & await

Clearly stolen inspired by C#

Identical syntax to C#

Under the hood, it's promises and generators

Think: resumable state machine

async & await

async function myFunc() {
  try {
    // all evergreen browsers, since Node 7.6
    let p1 = await callLib1(inp, args);
    let p2 = await callLib2(inp, args);
    let p3 = await callLib3(inp, args);
    return { p1, p2, p3 };
  } catch (err) {
    handleError(err);
  }
}

async & await

easy throw is easy

async function lib(inp, arg) {
  if (!inp) {
    throw new Error('inp is blank');
  }
  let res = await longRunningTask();
  return res;
}

Interop: async & Promise

easy Promise calls async fn

// lib.js
async function callLib(inp, arg) {
  if (!inp) {
    throw new Error('inp is blank');
  }
  let res = await longRunningTask();
  return res;
}
// main.js
callLib(inp, arg)
  .then(res => done(res))
  .catch(errorHandler);

Interop: async & Promise

easy await a Promise

// lib.js
function callLib(inp, arg) {
  return new Promise((resolve, reject) => {
    resolve(true);
  });
}
// main.js
async function () {
  try {
    let res = await callLib(inp, arg);
    return res;
  } catch (err) {
    handleError(err);
  }
}

Async evolution

evolve from callback to promise to async

Source: https://www.bram.us/2017/05/09/javascript-from-callbacks-to-promises-to-asyncawait-in-7-seconds/

Upgrading callbacks
to Promises

Shimming callbacks to Promises

Shimming callbacks to Promises

easy built-in util module

const promisify = require('util').promisify;
const newfn = promisify(callbackfn);

newfn(...)
 .then(...)
 .catch(...);

Shimming callbacks to Promises

hard doesn't follow convention

// BROKEN:
const promisify = require('util').promisify;
const sleep = promisify(setTimeout);
// can't do it, setTimeout doesn't follow convention

Shimming callbacks to Promises

const sleep = function(timeout) {
  return new Promise(resolve => setTimeout(resolve, timeout));
}
// ...
await sleep(100);
// ...

Shimming callbacks to Promises

const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
const sleep = function(timeout) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      resolve();
    }, timeout);
  }
};

promisify and callbackify

easy Pros

  • Simple wrapper
  • Easy to do in a loop

hard Cons

  • Only if it uses the convention

Express

Hello Express

// routes/index.js
const express = require('express');
const router = express.Router();
router.get('/', function(req, res) {
  // TODO: async stuff
  res.render('index');
});
module.exports = router;
// app.js
// ...
const index = require('./routes/index');
app.use('/path', index);
// ...

What we want

router.get('/', async function(req, res) {
  const result = await asyncStuff(arg);
  res.render('index');
});

What we have

router.get('/', function(req, res, next) {
  asyncStuff(arg, function (err, result) {
    if (err) {
      return next(err);
    }
    res.render('index');
  });
});

Express and async

easy Pros

hard Cons

  • Can't promisify
  • Can't throw

Solution

  • catch all exceptions
  • wrap async func
  • patch router maker