Skip to main content

Tagged “asynchronous”

Async and Await

My notes and reminders for handling promises with async and await In Real Life.

As I see it, the idea is to switch to using await when working with promise-returning, asynchronous operations (such as fetch) because it lends itself to more flexible and readable code.

async functions

The async keyword, when used before a function declaration like so async function f()):

  • defines an asynchronous function i.e. a function whose processes run after the main call stack and doesn’t block the main thread.
  • always returns a promise. (Its return value is implicitly wrapped in a resolved promise.)
  • allows us to use await.

The await operator

  • use the await keyword within async functions to wait for a Promise.
  • Example usage: const users = await fetch('/users')).
  • It makes the async function pause until that promise settles and returns its result.
  • It makes sense that it may only be used inside async functions so as to scope the “waiting” behaviour to that dedicated context.
  • It’s a more elegant syntax for getting a promise‘s result than promise.then.
  • If the promise resolves successfully, await returns the result.
  • If the promise rejects, await throws the error, just as if there were a throw statement at that line.
  • That throw causes execution of the current function to stop (so the next statements won't be executed), with control passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.
  • Given this “continue or throw” behaviour, wrapping an await in a try...catch is a really nice and well-suited pattern for including error handling, providing flexibility and aiding readability.

Here’s a try...catch -based example. (NB let’s assume that we have a list of blog articles and a “Load more articles” button which triggers the loadMore() function):

export default class ArticleLoader {

async loadMore() {
const fetchURL = "https://mysite.com/blog/";
try {
const newItems = await this.fetchItems(fetchURL);
// If we’re here, we know our promise fulfilled.
// We might add some additional `await`, or just…
// Render our new HTML items into the DOM.
this.renderItems(newItems);
} catch (err) {
this.displayError(err);
}
}

async fetchArticles(url) {
const response = await fetch(url, { method: "GET" });
if (response.ok) {
return response.text();
}
throw new Error("Sorry, there was a problem fetching additional articles.");
}

displayError(err) {
const errorMsgContainer = document.querySelector("[data-target='error-msg']");
errorMsgContainer.innerHTML = `<span class="error">${err}</span>`;
}
}

Here’s another example. Let’s say that we needed to wait for multiple promises to resolve:

const allUsers = async () => {
try {
let results = await Promise.all([
fetch(userUrl1),
fetch(userUrl2),
fetch(userUrl3)
]);
// we’ll get here if the promise returned by await
// resolved successfully.
// We can output a success message.
// ...
} catch (err) {
this.displayError(err);
}
}

Using await within a try...catch is my favourite approach but sometimes it’s not an option because we’re at the topmost level of the code therefore not in an async function. In these cases it’s good to remember that we can call an async function and work with its returned value like any promise, i.e. using then and catch.

For example:

async function loadUser(url) {
const response = await fetch(url);
if (response.status == 200) {
const json = await response.json();
return json;
}
throw new Error(response.status);
}

loadUser('no-user-here.json')
.then((json) => {
// resolved promise, so do something with the json
// ...
})
.catch((err) => {
// then() returns a promise, so is chainable.
// rejected promise, so do something with the json
document.body.innerHTML = "foo";
});

References:

Promises in JavaScript

A brief explainer (for future-me and anyone else it helps) on what promises are and how to use them. Note: this is not an official definition, but rather one that works for me.

In the normal run of things, JavaScript code is synchronous. When line 1 has run (perhaps defining a new variable) and the operation has completed, line 2 runs (perhaps operating on the variable we just defined). However sometimes in line 1 we need to do something which will take longer – maybe an unknown length of time – for example to call a function which fetches data from an external API. In many cases we don’t mind that taking a while, or even eventually failing, but we want it to execute separately from the main JavaScript thread so as not to hold up the execution of line 2. So instead of waiting for the task to complete and return its value, our API-calling function instead returns a promise to supply that value in the future.

A Promise is an object we use for asynchronous operations. It’s a proxy for the success value or failure reason of the asynchronous operation, which at the time the promise is created is not necessarily yet known.

A promise is in one of three states:

  • pending
  • fulfilled (the operation completed successfully)
  • rejected (the operation failed)

We say a pending promise can go on to be either fulfilled with a value, or rejected with a reason (error).

We also talk about a promise being settled when it is no longer pending.

One memorable analogy was provided in Mariko Kosaka’s The Promise of a Burger Party. In it she describes the scenario of ordering a burger from, say, Burger King. It goes something like this:

  • you place your order for a Whopper;
  • they give you a tray with a buzzer. The tray is a promise that they will provide your burger as soon as it has been cooked, and the buzzer is the promise‘s state;
  • the buzzer is not buzzing to start with: it’s in the pending state;
  • the buzzer buzzes: the promise is now settled;
  • they might inform you that their flame-grill broke down half-way through. The cooking operation failed, and the promise of a burger has been rejected with that reason. You’ll likely want to act on that (by getting a refund);
  • alternatively all goes to plan, you go the counter and they fulfil their promise of a tasty burger placed onto your tray.
  • you decide you want to act on the success of getting your tasty burger by doing another thing, namely buying a coke. In code you’ll be within the .then() method which is the success handler for your promise and in there you can just call buyCoke(). (Note: the buyCoke() operation might be synchronous; the real life analogy being that it’s so quick to pour a coke that the assistant does it and serves it on a tray immediately rather than giving you a tray as a promise for it.) At the end of your then() you might choose to return a meal object which combines your burger and coke.
  • You could then chain a further .then() to the original .then() to work with your snowballing data value. This is because then() methods always return a promise (even if the operation within was not asynchronous), and we already know that promises are thenable (i.e. have a then() method) hence our ability to keep chaining.

Promise syntax

There are two things we need to be able to do with promises:

  1. create and return them;
  2. work with a returned promise.

Create and return a promise

When you create a promise you pass a function known as the executor to it, which runs instantly. Its arguments resolve and reject are callbacks provided by JavaScript. Our code goes inside the executor.

let examplePromise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…

if (/* everything turned out fine */) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});

Work with a returned promise

examplePromise.then(function(response) {
console.log("Success!", response);
})
.catch(function(error) {
console.log("Failed!", error);
})

References

See all tags.

External Link Bookmark Note Entry Search