|Managing Asynchronous Code - Callbacks, Promises & Async/Await|
|Written by Mike James|
|Wednesday, 12 August 2015|
Page 2 of 3
So, no computer science, no implementation details and no adding promises to existing asynchronous code. Instead we are going to assume that we live in a world where all asynchronous code uses promises and it is just up to us to code using this fact.
Instead of accepting a call back the asynchronous function returns a Promise object. This can be in three possible states:
Once a Promise object enters either the fulfilled or rejected state it doesn't change state again.
The eventual result of a promise is always a single value - which sounds restrictive until you know that it can be an object.
returns a Promise object imediately and gets on with whatever it is supposed to be doing to get the eventual result.
It is often said that a Promise is a promise to return a result.
So how do you get to process the result?
The then method of the Promise object is the most common way to process the eventual result.
When the Promise object enters the fulfilled state the functions registered with the then methods are executed with the result as their single parameter.
You can register multiple functions with a Promise object and they will be executed in the order that they were registered.
If a Promise object is in the fulfilled state when you register a function then it will be called almost immediately.
The reason why it is "almost" immediately is that all registered functions are called asynchronously and hence the function that is using the Promise has to end before any "then" functions are called.
All registered functions are called just once.
even if the asyncFunc is completed before the functions are registered.
So far the Promise looks a lot like just a neater way of setting up a callback - and in many ways that really is all it is but there are some other advantages in doing things this way.
The Promise object is returned immediately to the calling function and this can be used to set up complicated call sequences that will only happen long after the calling function has completed.
For example you could set two async operations going using:
in this case which of the two would finish first isn't determined and the messages will be printed on the console in any order.
If you want to ensure that asyncFunc1 finished and then asyncFunct2 was called you need to use the fact that the then method returns a Promise object that resolves to the return value of the called handler.
That is then() returns a Promise for the value that its handlers, the code it eventually runs, return.
This is a complicated idea but it means if the function that you use in then() returns a value generated by an asynchronous function then the Promise that then() returns waits for the async function to complete.
Now we have a situation in which the console log will always read "first" printed after asyncFunc1 completes and then "second" after asyncFunc2 completes.
Using this chaining approach you can write a sequence of asynchronous actions which look a look like the code used in the synchronous case.
You can even start the chaining with the first function call and hide the Promise object completely:
Which looks a lot like the synchronous
As long as you have a good imagination.
onFulfilled and onRejected
Things are even better however because the then method has two possible handlers - one for the fulfilled state and one for the rejected:
and there is a catch method which allows an onRejected handler to be added to a Promise to handle an error condition that has been cascaded down the chain.
How this all works is subtle.
The key is that the then() method returns a Promise that resolves to either the return value of the handler that was run or to the state of the Promise one earlier in the chain if the handler was undefined. You can see that this could lead to propagation, mostly of unhandled error conditions down the chain.
This gives you a lot of scope in handling errors you can choose to handle each error as it occurs:
Notice that when an error handler is called the next then recieves a new Promise resolves to the return value of the error handler. This results in the onFulfilled handler being called imediately and the chain continues as if there was no previous error.
To bring the chain to a halt on an error you have to leave the onRejected handlers undefined until you are ready to handle the error. For example:
If an error occurs in the call to asyncFunc1 then the Promise it returns is rejected and as there is no onRejected handler the Promise the then() generates is also rejected and so on till we reach the first onRejected handler.
In most cases the final Promise generated has an onRejected handler added to it using the catch method.
You can see that this is vaguely similar the way an exception propagates until it reaches a handler and so we have a reasonable approximation to synchronous error handling.
Once you move beyond these basics there are methods that allow you to combine Promise object in various ways that mimic simple synchronous flow of control.
For example the all method returns a Promise when all of the specified promises have resolved. The race method returns a Promise when any one of the specified promises has resolved.
There are other non-standard or almost standard methods that combine promises in this way.
|Last Updated ( Friday, 31 March 2017 )|