I’ve always found promises rather confusing.
After recapping it for the nth time this morning, I would like to detail here my main takeaways so that I can finally commit them to memory.
Recap on Promises
Firstly, to understand promises, you first have to understand threading. Javascript is single-threaded, meaning that all code execute line by line, one after another.
When you create a promise, you’re essentially creating another thread that is added to the event queue and importantly, executed after your main code block.
Essentially, this effectively means that they function something like effect hooks in React.
The implication of this is that you cannot access objects returned by promises in the main code block! All references have to be in the then statement.
Second, the two intermediate states of the promise are: resolved and rejected. When it is resolved, you can access the response object via the .then() statement. If it’s rejected, you can access the error object via the .catch() statement.
Async-await
Now, when you create an async function, it returns a promise. You can then handle it like how you’d normally handle promises - in .then() statements.
It basically says: take this function off the main thread and into async event queue.
| |
The method above can be known as .then approach to promises.
The other way to access the data of promises is to use the await method.
Await allows you to code as if its synchronous by pausing your code on the line where you attach await in front of any async promise-based function. The result is returned.
For instance
| |
let response = await fetch(‘coffee.jpg’) will cause code execution to pause here, until asset is fetched. Then, myBlob will run - and code is paused there as well until myBlob is fetched, afterwhich objectURL line executes.
Different methods of calling Async/await and its relation to time
When you await a promise based async method, the code is paused until the promise is resolved or rejected.
The following code will take 9 seconds:
Method 1 - normal await call
| |
However, when you store the promise methods in variables and call await on the variables, you essentially create multiple simultaneous threads.
The following code takes 3 seconds:
Method 2 - variable call
| |
When you store the Promise objects in variables, it ‘has the effect of setting off their associated processes all running simultaneously’.
Error Handling
Regular promises
There are two ways to handle error for promises. For regular promises, use .then().catch(). If promise is rejected, .catch() will run.
For example:
| |
Async-await
For async-await, use try-catch to catch errors.
| |
Further subtleties on working with Promises
This article (https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html) is a really good resource on the subtleties of working with promises.
Basically, he posits that there are only three things you do inside a .then() function.
| |
- return another promise
- return a synchronous value (or undefined)
- throw a synchronous error
1. Return another promise
| |
Here, the return is crucial because if not, the getUserAccountById() would actually be a side effect, and the next function would receive undefined instead of the userAccount.
2. Returning a synchronous value
| |
Here, the function will return an inMemoryCache of user if it’s present. That is a synchronous value. If not, return the promise.
In some ways, .then() functions similar to await, in that it allows us to control execution flow.
Author says to: I make it a personal habit to always return or throw from inside a then() function because non-returning functions in JS technically returns undefined.
3. Throw a synchronous error
| |
Our catch() will receive a synchronous error if the user is logged out, and it will receive an asynchronous error if any of the promises are rejected. Again, the function doesn’t care whether the error it gets is synchronous or asynchronous.
This is especially useful because it can help identify coding errors during development.