Skip to content

Instantly share code, notes, and snippets.

@Jossdz
Last active August 16, 2019 21:32
Show Gist options
  • Save Jossdz/98091dfadeff01af22dd62e82d0bb065 to your computer and use it in GitHub Desktop.
Save Jossdz/98091dfadeff01af22dd62e82d0bb065 to your computer and use it in GitHub Desktop.
TOPSECRET

JS | Async - Await

Learning Goals

After this lesson you will be able to:

  • Understand the javascript asynchrony history.
  • Understand why async/await it’s important.
  • Explain the difference between old asynchrony handling and async-await.
  • Solve pending promises with async-await.

Introduction

As you know javascript it’s an asynchronous programming language, that means that javascript can perform actions (code executions) with timing difference. In other words asynchrony means the difference between now and later.

With that in mind, let’s talk about the mechanisms for handle async code, code that will do something in the future. In javascript history we’ve had several ways to handle this async code as Callbacks and Promises.

Callbacks

Callbacks are a functions which are passing as an argument, and its invoke inside the function for complete an action. That’s the proper definition but callbacks also helps when we need to solve async code, when an async operation has a result , (a result being either returned data or an error that occurred during the operation), it points to a function that will be invoked once that result is ready.

	function sayHello(name){
		alert(`Hello ${name}`)
		}
	function getUserName(callback){
		const name = prompt(`what's your name?`)
		callback(name)
	}
	getUserName(sayHello)

In the previous code we have two functions the first one it’s called sayHello and only takes a name to execute an alert with it, the second one it’s a function that takes a function as an argument (a callback), prompts the user name then invoke the callback function with the name.

This is great, but let’s take a look into the pros and cons of handle async code with callbacks.

Callback Pros

  • Callbacks are an easy way to solve this async operations.
  • There are a lot of libraries written with callbacks.

Callback Const

  • Callbacks have a hard to read structure: this means that callbacks can easily confuse other developers and their structure make us read callbacks sequentially even when there are no sequence.
doA(() => {
	doB()
	doC(() => {
		doD()
	})
	doE()
})
doF()

When you see the previous code you can easily think that the execution flow follows this sequence: A, B, C, D, E and F. But… no.

first(() => {
	third()
	fourth(() => {
		sixth()
	})
	fifth()
})
second()

This code above show the real execution flow.

  • Callbacks have a common problem called callback hell which is a nested callback execution queue:
functionA(() => {
	functionB(() => {
		functionC(() => {
			functionE(() => {
				functionN(){...}
			})
		})
	}) 
})
  • Handmade error handling:
myFunction(num, function callback(err, result) {
  if (err) {
    return myFunction(num, callback);
  }
  // handle result
})

Promises

Promises are objects used for async processes. A promise have a value that can be available now, in the future or never.

	new Promise((resolve, reject) => {
		if(true){
			resolve(/* Response */)
		}else{
			reject(/* Error */)
		}
	})

As you can see the promise receives a callback function with two arguments: resolve and reject, used to make succeed the promise or reject the promise.

A promise is found in one of the following states:

  • Pending (pending): initial status, not met or rejected.
  • Completed: means that the operation completed successfully.
  • Rejected (rejected): means that the operation failed.

To use a promise result you could use the then and catch methods.

	promise
		.then(response => console.log(response))
		.catch(error => console.error(error))

This is how we solve promises, then and catchmethods needs a callback to work, thenreceives an argument with the value returned from the resolve method parameter. catchmethod also receives the error from the reject method.

This approach looks so difficult, but in most cases you will just solve already created promises from libraries built with promises support for their async operations. For example libraries for Ajax calls, db connection or filesystem.

Promises pros

  • It’s easy to use (easier than callbacks).
  • Most libraries are now built in promises.
  • Easy error handling.
  • Sequential execution.
  • Return promises that can be chained.
  • Have methods to multiple promises or resolve one promise first (all and race).
  • Improves readability.

Promises cons

  • Still use callbacks.
  • There are a promises hell too.

async / await

Async/await its a new feature included in ES6+ for handle Promises, we can say that its sugar syntax for handle promises.

The difference it’s pretty simple, now we have to use a function or identify the function that needs to solve an async operation and mark it as an asynchronous function with the async keyword:

async function someFunction(){
	//...
}
/* or */
const someFunction = async () => {
	//...
}

This new keyword async allows you to use another keyword inside the function called await to await (as it name says) an asynchronous operation response:

async function someFunction(){
	const response = await db.find()
}
/* or */
const someFunction = async () => {
	const response = await db.find()
}

Our new notation have a couple of thing to notice, the first one is that await keyword pause the execution flow until the promise is fulfilled, once this happens continues with the execution. The second one is that the error handling it’s not intuitive or included with the notation, to handle errors with async await we have several options.

Async await error handling

try - catch

The first solution its wrap the await call with a try block and add the catch method. This catch method will catch any error occurred in the try block:

async function someFunction(){
	try{
		const response = await db.find()
	}catch(error){
		console.error(error)
	}
}
/* or */
const someFunction = async () => {
	try{
		const response = await db.find()
	}catch(error){
		console.error(error)
	}
}

The catch block will receive any error thrown inside the try block and you can easily handle the error now.

function invoke - catch

At the functions invokes there are something we missed along which is the catch method, this is an avoided feature but super useful.

async function someFunction(){
	const response = await db.find()
}
/* or */
const someFunction = async () => {
	const response = await db.find()
}

someFunction().catch(error => console.error(error))

Error handling functions

Since async/await have not strict way to handle errors you can use a handmade function to solve errors with then/catch and re-use it.

export default function to (promise) {
	return promise
		.then(data => [null, data])
		.catch(err => [ err ])
}

const someFunction = async () => {
	const response = await to(db.find())
}

The best thing about this is that you don’t have to write this code in every project you develop, there are libraries like await to js to re-use.

Refactor then to async/await

A useful practice to understand and learn how and when to use async / await it’s refactor then structure. This is easy but it turns pretty straightforward with this five steps:

  1. Identify the function that it’s performing an async operation.
// this 👇 is the function that is executing an asynchronous operation.
function getResource(){
		DB.find()
			.then(response => console.log(response))
			.catch(error => console.error(error))
	}
  1. Once we identify the asynchronous function we have to place the async keyword before the function keyword or the parentheses in an arrow function.
 /* 👉 */async function getResource(){
		DB.find()
			.then(response => console.log(response))
			.catch(error => console.error(error))
	}
  1. The next step now we already marked our function as an async function is identify the callback argument name (in the thenmethod) and create a new variable/constant with it.
	// step one
 async function getResource(){
		DB.find()
			.then(/* 👉 */response => console.log(response))
			.catch(error => console.error(error))
	}
// step two
async function getResource(){
		const /* 👉 */ response
		DB.find()
			.then( => console.log(response))
			.catch(error => console.error(error))
	}
  1. We already use the asynckeyword in our code, it’s time to use await. So let’s add the awaitkeyword between the = and the promise. After that we should assign to our variable/constant the promise
async function getResource(){
		/* 👉 */ const response = await DB.find()
			.then( => console.log(response))
			.catch(error => console.error(error))
	}
  1. The last step is remove just the then structure (not the code block) and the full catch (you can handle the error as you want).
async function getResource(){
	const response = await DB.find()
	console.log(response)
}

This is the easiest way to refactor old promises resolver functions and the best way to get use to implement async/await.

Async/await pros

  • We have a synchronous looking like code.
  • Sequential and stepped solutions for async operations.
  • Less code.
  • we can just await an async operation without save the result.

Async/await cons

  • Doesn’t have a consolidated error handling structure.
  • Always needs a function to wrap the async operations (we can always wrap the code inside a function or an IIFE).

Summary

The history of asynchronous code in javascript it’s interesting and we can easily note the progress of the language with this brand new async/await operators that allows you to write better code, less code and understandable code. The pure existence of async/await doesn’t mean that the rest of solutions are bad, it’s just another way to resolve any async operation.

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment