Async in JS

What does Asynchronous mean in Javascript

When you have a long-running synchronous code (e.g. generate 10,000,0000 prime numbers), JS code will be stuck until this code completes because JS is single threaded.

Async allows a long-running operation to return immediately with a Promise to be resolved in the future.

The thread will continue to the rest of the program, then resume when the long-running operation completes (resolves).

Promise

A Promise represents the future result of an asynchronous operation.

.then()

Handles resolved or rejected value of a Promise.

Takes in two parameters:

  • callback functions for fulfilled Promise and rejected Promise
function api() { return new Promise((resolve, reject) => { // ... }) }

Using .then() can write asynchronous code in a way that resembles synchronous code which is easier to follow.

const promise1 = new Promise((resolve, reject) => { resolve("Success!"); }); promise1.then((value) => { console.log(value); // "Success!" });

Chaining

.then stores the callbacks within the Promise it is called on and immediately returns another Promise object, allowing you to chain calls to other Promise methods.

api() .then(function(result){ return api2(); }) .then(function(result2){ return api3(); }) .then(function(result3){ // do something with result3 }) .catch(function(error) { //handle any error });
  • catch is same as a try { ... } catch block structure.

If the return value within .then() is not a promise, it's implicitly wrapped in a Promise and then resolved.

const p2 = new Promise((resolve, reject) => { resolve(1); }); p2.then((value) => { console.log(value); // 1 return value + 1; }).then((value) => { // synchronous value works console.log(value); // 2 }); p2.then((value) => { console.log(value); // 1 });

Promise vs Callback

Promises are NOT Callbacks

Before Promise, Async JS codes relied on callback functions to be executed after a desired time.

  • Although both Promise and callbacks can be used to achieve the async, they are fundamentally different things.

Chaining multiple asynchronous operations after other in a row with callback resulted in the pyramid of doom.

doSomething(function (result) { doSomethingElse(result, function (newResult) { doThirdThing(newResult, function (finalResult) { console.log(`Got the final result: ${finalResult}`); }, failureCallback); }, failureCallback); }, failureCallback);

With promise, you break out of the pyramid shape

const promise1 = doSomething(); const promise2 = promise1.then(successCallback, failureCallback);

async

Syntactical sugar for Promise.

// Arrow Function const foo = async(arr, callback) => { // use arr and callback } // Function Declaration async function foo() { // ... await ... } // anonymous async in callback const foo = event.onCall(async() => { // ... }) // in React async function fetchMovies() { const response = await fetch('/movies'); // waits until the request completes... console.log(response); } // example of useEffect in React useEffect(() => { const fetchData = async () => { // some code like fetch() }; await fetchData() .then( (res) => // ... ) .catch( (e) => console.error(e) );; }, [])

return in async

return

return in async function wraps the return value in Promise.resolve.

  • wrapped Promise of the value is returned.

Non-Promise return

Expression with non-promise value will be converted to a promise.

async function fn() { const a = await 9; } // becomes async function fn() { const a = await Promise.resolve(9); }

Missing return

async function foo() { try { canRejectOrReturn(); // no return } catch (e) { return 'caught'; } }

Async function without a return statement (like foo) will resolve with undefined.

Promise will STILL execute even without a return.

If your promise function has a side effect, the side effects will execute.

Async vs Promise

Main difference is the scope

Promise is kicked off right as they are defined, whereas async/await will execute one Promise at a time.

// Operation A & Operation B can run in parallel Promise.all([ returnsAPromise(opA), returnsAPromise(opB) ]) .then(res => { // waits info from both Operations A & B console.log("done") } ); // With Async // Operation A executes first, then Operation B const asyncFn = async () => { // Operation A runs first const resultA = await returnsAPromise(opA); // Operation B runs after Operation A completes const resultB = await returnsAPromise(opB); // Then, Operation C,D,E... runs console.log("done"); } asyncFn();

When using await, thread of execution will pause at the line containing await until the Promise is resolved.

  • Even the synchronous code will wait until the await is complete.
async function example() { console.log("Step 1"); // synchronous await doSomethingAsync(); // pauses here until resolved console.log("Step 2"); // won't run until above promise resolves }

Synchronous vs Parallel Execution

When you have multiple awaits in a single function, an expression will run synchronously.

const test1 = async () => { const delay1 = await Promise.delay(600); //runs 1st const delay2 = await Promise.delay(600); //waits 600 for delay1 to run const delay3 = await Promise.delay(600); //waits 600 more for delay2 to run };

To parallel process multiple async/await, we use it with Promise.all() and combine scope via destructuring:

const asyncFnParallel = async () => { // Operation A & Operation B can run in parallel const [resultA, resultB] = await Promise.all([ returnsAPromise(opA), returnsAPromise(opB) ]); // Operation C,D,E... runs after Operation A and Operation B console.log("done"); } asyncFnParallel();

Execution Behavior of Promise vs Async

Promises run automatically without being called

You don't "declare" Promises:

  • new Promise creates a Promise.
    • calls the executor function you pass it, synchronously, before new Promise returns.

If you want to define a Promise, but don't want to start it until specific point in time, you define it inside async function:

async function startPromise() { // ...other Promise can sit here too } let processPromise = startPromise();