-- Leo's gemini proxy

-- Connecting to capsule.adrianhesketh.com:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; charset=utf-8

capsule.adrianhesketh.com


home


Migrating to async/await (Node.js / AWS Lambda / Serverless Framework)


As I mentioned (//2017/07/27/serverless-web-apps-without-client-side-javascript) I'm using the Serverless Framework (serverless.com) to build a new product using Node.js.


I'm fairly new to node, but there was a feature I was really missing, and that's async/await which I think makes Node.js much less cluttered with boilerplate syntax than using callbacks or promises.


When I first tried to use it I got a syntax error. _Of course!_ I should have noticed that the version of Node.js that AWS Lambda currently supports (6.10.3) doesn't include async/await.


Surely there's an easy way to get async/await to work with Serverless?


What are callbacks, promises and async/await anyway?


Callbacks came first, then Promises (in their various guises) then finally async/await. It looks like quite a bit of a mess to anyone who's coming fresh to Node.js, but on the other hand, I've been using a similar feature in C# since 2012, and it's not really any different to the situation there - delegates / event handlers for handling events in the early days (callbacks), then later lambda expressions and `Task` were added (promises), then the language got async / await on top to make the code clearer.


Since there's 3 ways of achieving the same objective, Node.js libraries use all of the different mechanisms depending on how old they are.


I must have missed the official promises developments entirely, since there were a number of various ways of doing it floating about. Fortunately there's a really good interactive guide at [0] which explains how the different systems work and why they're important. This was really helpful for me.


[0]


This blog was also handy to guide me in how I could convert code I'd written which used callbacks to using promises. [1]


[1]


Once I had my code using Promises, I wanted to step up to async/await to simplify more. Since I can't update Node.js on AWS, I'd need to update my code somehow.


Babel steps up


From doing a bit of React.js, I knew that modern javascript code needs to be "transpiled" (i.e. modified / converted) to run on anything but the very latest Web browsers and that Babel [2] is often used to do this.


[2]


So, I knew I needed to get Babel to convert my modern javascript to javascript suitable to run on an older Node.js runtime.


I didn't find Babel's documentation particularly helpful, and a lot of the suggestions on Stackoverflow etc. are out-of-date with current best practice, but thanks to a few issues and questions on Stackoverflow, I worked out what I needed to do:


- Install Babel into my project (`npm install --save-dev babel-cli` and `npm install --save-dev babel-preset-env`)

- Add a `.babelrc` file to tell Babel what rules to apply (in my case, compile to the version of node that the current environment is using - which I've also set to be the same version that AWS Lambda uses)


- You've got to be careful here. If the Node.js version on the machine that's transpiling the code is newer than the target version, then Babel won't do the correct operations.


- Update the `packages.json` to add scripts in to:


- Run the Babel executable against my Node.js code


- Ignore the node_modules folder so that it doesn't attempt to transpile everything.

- Ignore the output folder.


- Run tests against the newly transpiled output.


- Update the Serverless.yml to use the transpiled output (under the /lib directory) which is compatible with the AWS Node.js version.


Migrating the code to use async/await


Once I had support for it, I needed to refactor my code. Here's an example of the process I used to migrate some code I wrote to read from Google's Geolocation API and parse the output into a Location class.


Callbacks


module.exports.postcodes = (apiKey, postCode, callback) => {
  const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${escape(stripWhitespace(postCode))}&key=${escape(apiKey)}`;

  console.log(`postcode: ${url}`);

  fetch(url)
    .then(response => response.json())
    .then((s) => {
      console.log(`postcode: retrieved data: ${JSON.stringify(s)}`);
      const rv = new Location(s.results[0].geometry.location.lat,
        s.results[0].geometry.location.lng);
      callback(null, rv);
    })
    .catch((error) => {
      console.log(`postcode: error: ${error}`);
      callback(error, null);
    });
};

Promises


module.exports.postcodes = (apiKey, postCode) => new Promise((resolve, reject) => {
  const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${escape(stripWhitespace(postCode))}&key=${escape(apiKey)}`;

  console.log(`postcode: ${url}`);

  fetch(url)
    .then(response => response.json())
    .then((s) => {
      console.log(`postcode: retrieved data: ${JSON.stringify(s)}`);
      resolve(createLocationFromGoogleResponse(s));
    })
    .catch((error) => {
      console.log(`postcode: error: ${error}`);
      reject(error);
    });
});

async/await


module.exports.postcodes = async (apiKey, postCode) => {
  const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${escape(stripWhitespace(postCode))}&key=${escape(apiKey)}`;
  console.log(`postcode: ${url}`);
  const response = await fetch(url);
  const locationData = await response.json();
  console.log(`postcode: retrieved data: ${JSON.stringify(locationData)}`);
  return createLocationFromGoogleResponse(locationData);
};

Usage


function exampleCallback(a, b, callback) {
	callback(null, a+b);
}

function examplePromise(a, b) {
	return new Promise((resolve, reject) => resolve(a + b));
}

exampleCallback(1, 2, function(err, result) {
	console.log(result);
});

// Doesn't work at all. Callbacks and promises are not backwards compatible.
examplePromise(3, 4, function(err, result) {
        console.log(result);
});

// Promises are handled with then and catch.
examplePromise(5, 6)
	.then(results => console.log(results))
	.catch(err => console.log(err));

// But it's possible to await the promise instead inside an async function.
// console.log(await examplePrommise(7, 8));

The usage example shows that it's important to note that the API surface changes when migrating from callbacks to Promises, so calling code _will_ be needed for that, just not from when migrating to async / await.


Conclusion


I think it's worth the effort of back-porting async/await to AWS Lambda to make code much easier to read, despite the added complication of a build step.


Sources


- https://javascript.info/callbacks

- https://benmccormick.org/2015/12/30/es6-patterns-converting-callbacks-to-promises/

- https://github.com/babel/babel/issues/5532

- https://stackoverflow.com/questions/35748116/babel-ignore-several-directories?noredirect=1&lq=1

- https://stackoverflow.com/questions/10753288/how-to-specify-test-directory-for-mocha

- https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined

- https://github.com/babel/babel-preset-env


More


Next


lexical - a library for parsing streams in Go with parser combinators


Previous


Building and shipping .NET Core 2.0 applications on Circle CI with Docker and Amazon ECR


Home


home

-- Response ended

-- Page fetched on Sun Apr 28 09:20:25 2024