JavaScript: Promise or async-await? Async without await, Await without async?
The reason for this post is because I had some problems with wrapping my head around JavaScript asynchronous programming. I spent 4 hours scratching my head while fixing a bug in my game. Even though I had some basic experience with asynchronous programming when I worked with .Net, but many concepts in JS still got me confused (I’m not blaming the language tho, considering that I didn’t actually study JavaScript “the right way”).
It would have been faster if I had a basic grasp of JS asynchronous, that’s why I decided to summarize what I (may) have understood in this post.
JavaScript Basis
First, JavaScript is synchronous, blocking, single-threaded.
Which means if you’re executing a JS block of code on a page, then no other JavaScript on that page will be executed, only one operation can be processed at a time.
But what if we want to execute a function after another function has finished executing? That’s where we have callback.
Callback
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
Callback can be synchronous or asynchronous
Synchronous callback:
Example #1:
|
|
|
|
Here foreach
function takes a callback and execute it synchronously, as foreach does not wait for promises.
Example #2:
|
|
|
|
Notice the order: ‘Done with callback’ => ‘After callback’ => ‘After functionTop’, showing that the callback call is synchronous, all functions execute in the order they are called.
Asynchronous callback
Example #3:
|
|
|
|
I used setTimeout
to make myFunction
run asynchronously, then pass a callback function into it.
x
still remains false
, this is because any function given to the setTimeout
function will be executed asynchronously, when the main thread is not busy.
Another example #4:
|
|
|
|
As I mentioned above, foreach
function takes a callback and executes it synchronously. setTimeout
returns a value immediately, then the next line begins to execute, but the asynchronous callback (setTimeout
) will be executed later.
Promise
Promise is a way to implement asynchronous programming in JavaScript. Promise was introduced in ES6 (ES2015).
MDN’s definition: The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Differences between callback and promise
- Callback is function, promise is object
Callback is a block of code which can be executed in response to events such as timer etc.
Promise is an object which stores information about whether the event has happened or not, if it has, what was the result.
- Callback is passed as an argument, promise is returned
Callback is defined independently, is passed as an argument, and is stored in the function where it is called from.
Promise is created inside of asynchronous functions and then returned.
- Callback can represent multiple events, promise represents one
Callback can be called multiple times by the function it is passed to.
Promise represents one event, a promise can only succeed or fail once.
Example #1:
|
|
Running new Promise
will immediately call the function passed in as an argument (here it means setTimeout
is called immediately).
Promise constructor takes one argument: a callback with two parameters, resolve
and reject
.
You can use the promise result when it becomes settled with 3 methods: then
, catch
, finally
.
These methods also return a newly generated promise object, which can optionally be used for chaining.
Example #2:
|
|
async - await
ES2017 (ES8) introduced the concept of async
functions and await
keywords, a higher level abstraction over promises.
They are just syntactic sugar on top of promises, making asynchronous code easier to write and to read.
First we have the async
keyword:
- When you put
async
in front of a function declaration, it turns the function into an async function - Async functions can contain zero or more await expressions
- Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise
Then, the await
keyword:
await
only works inside async functions- Causes async function execution to pause until a promise is settled
- If the Promise is rejected, the await expression throws the rejected value.
- To await a function, that function must return a promise
Example:
|
|
The function execution will “pause” at the line where we used await
, and resume when the promise settled.
Await a function without async
Since async
is still promise-based, we can await
a function that returns a promise, even when that function is not an async
function:
|
|
The below code has the same result:
|
|
Async function without await inside
We can declare a function as async
without using any await
. In this case, the execution is not paused and your code will be executed in a non-blocking manner (asynchronous - no waiting). It is the same as not declaring that same function with async
.
|
|
|
|
You can remove async
keyword before f1
and still get the same result.
With await
:
|
|
|
|
Calling async without await
Be careful when calling an async
function without await
:
|
|
|
|
Since f1
is asynchronous, when it is executed, the line x = 3;
executes immediately without waiting.
References:
MDN - Making asynchronous programming easier with async and await
Introduction to ES6 Promises – The Four Functions You Need To Avoid Callback Hell
Stack Overflow - Await doesn’t wait
Stack Overflow - Async function without await in Javascript
Async functions - making promises friendly
Stack Exchange - How does Javascript code become asynchronous when using callbacks?