Asynchronous Programming & Promise
Junghoo Cho
Traditional Programming
Multi-Threading
Traditional solution to multiple request processing
Create one thread per each request
Invoke multiple request handlers in parallel
“No change” in coding style
Structure of each request handler remains the same
Used by most traditional servers, including Apache, Tomcat
But multi-threading incurs significant resource overhead
Memory use (~ 10MB per thread)
Thread invocation overhead
Concurrency handling logic: semaphore, lock, …
Single-Threaded JS Engine
JavaScript runs in a single thread
Node.js and browser JavaScript engines
Cannot use multi-threading
Use one thread to handle all requests
No need to worry about concurrency
More efficient resource usage in principle
But potentially long waits at blocking calls
Asynchronous API
“Nonblocking” API for “multi-processing” under the single-threaded environment
Do not wait, return immediately!
Invoke callback function when ready
Example: db.find({userid: id}, callback);
db.find()
returns immediately (no blocking)
callback
is invoked when the database object is ready
The retrieved object is passed as a parameter to callback
callback
can perform actions with the object
Synchronous vs Asynchronous
Synchronous
Asynchronous
user = db.find({userid: id});
db.find({userid: id}, callback);
Wait until when everything is ready
Return immediately , callback
when ready
Next line in the code has the required object
We can do what we logically need to do in the next line
Next line in the code does not have required object
We cannot do what we logically need to do in the next line
All logical sequence of actions in one function
Actions are spread across multiple callback functions
Callback Hell
function sendPicture (id ) {
db.find({userid : id}, callback1);
}
function callback1 (err, user ) {
fs.readFile(user.picFile, callback2);
}
function callback2 (err, picture ) {
socket.write(picture, callback3);
}
function callback3 ( ) {
console .log("done!" );
}
Difficult to see the logical sequence of actions
Very different from traditional style of programming
- Most asynch API uses the convention of
1. taking the callback function as the last parameter and
2. passing (err, obj) as two parameters to the callback function
- `socket.write()` is for an illustration purpose. It is not a real node.js function
Nested Callback Function
function sendPicture (id ) {
db.find({userid : id}, (err, user ) => {
fs.readFile(user.picFile, (err, picture ) => {
socket.write(picture, () => {
console .log("done!" );
})
})
})
}
Better, but still ugly, difficult to understand, and easy to make mistakes
New ECMAScript language constructs
Promise (ECMAScript 2015)
async
/await
(ECMAScript 2017)
Most confusing part of this class
Promise (ECMAScript 2015)
let prom = db.find({userid : id});
prom.then(fulfillCallback[, rejectCallback]);
An asynchronous function immediately returns a “promise”
Once a promise is obtained, callback can be attached using then()
The callbacks will be called when the operation is completed
If success, fulfillCallback
is called with “result of operation”
If failure, rejectCallback
is called with “error value”
Q: How is it better?
We are doing the same thing in two steps not one! This looks worse!
Promise Chain
function sendPicture (id ) {
let prom1 = db.find({userid : id});
let prom2 = prom1.then(user => fs.readFile(user.picFile));
let prom3 = prom2.then(picture => socket.write(picture));
let prom4 = prom3.then(() => console .log("done!" ));
}
- Parallel Promise
- `Promise.all(p1, p2, ...)`
- Wait for all promises to be fulfilled, or for any to be rejected.
- The settled value is the array of fulfilled values or the error value from rejection
- `Promise.allSettled(p1, p2, ...)`
- Wait until all promises have settled (each may resolve or reject).
- The settled value is the array of settled (either fulfilled or rejected) value
- `Promise.race(p1, p2, ...)`
- Wait until any of the promises is fulfilled or rejected.
- The settled value is the first settled value
Details on Settling Promise
let prom1 = db.find({userid : id});
let prom2 = prom1.then(fulCB1, rejCB1);
let prom3 = prom2.then(fulCB2, rejCB2);
Terminology: A promise is settled either by being fulfilled (= resolved) or rejected
Q: How is prom1
is settled?
A: Depending on what happens from db.find()
If success, prom1
is fulfilled to the output. fulCB1()
is called with “db object.”
If failure, prom1
is rejected to error. rejCB1()
is called with error.
Q: How will prom2
be settled?
A: Depends on what happens from callbacks (fulCB1
or rejCB1
).
Q: What if the callbacks return a (regular) value?
A: prom2
is fulfilled to the value. fulCB2(value)
is called.
Q: What if the callbacks throw an error?
A: prom2
is rejected to the error. rejCB2(error)
is called.
Q: What if the callbacks return a promise p
?
e.g. let prom2 = prom1.then(user => fs.readFile(user.picFile));
A:
If p
is fulfilled to value
, prom2
is fulfilled to value
. fulCB2(value)
is called.
If p
is rejected to error
, prom2
is rejected to error
. rejCB2(error)
is called.
Promise Chain: Rejection Forwarding
function sendPicture (id ) {
db.find({userid : id})
.then(user => fs.readFile(user.picFile))
.then(picture => socket.write(picture))
.then(() => console .log("done!" ))
.catch(errorHandler);
}
Sometimes a promise may be rejected
Q: What if a promise is rejected, but rejection callback is not set?
A: If a rejection is not handled, it is forwarded to the next then()
Setting one rejection callback at the end will be enough
No need to set a rejection callback in every then()
then(null, rejectCB)
can be abbreviated to catch(rejectCB)
- Parallel Promise
- `Promise.all(p1, p2, ...)`
- Wait for all promises to be fulfilled, or for any to be rejected.
- The settled value is the array of fulfilled values or the error value from rejection
- `Promise.allSettled(p1, p2, ...)`
- Wait until all promises have settled (each may resolve or reject).
- The settled value is the array of settled (either fulfilled or rejected) value
- `Promise.race(p1, p2, ...)`
- Wait until any of the promises is fulfilled or rejected.
- The settled value is the first settled value
Error Handling in Promise Callback
Guarantees of Promise
Callbacks added with then()
even after the success/failure of the asynchronous operation will be called
Callbacks will never be called before the completion of the current run of the JavaScript event loop
The reason for the name “promise”
The promise that the async operation will be completed
The promise that the correct callback will always be called later
“Promisified” Asynchronous API
Some APIs have been modified to return a promise if no callback
e.g., MongoDB node.js driver
Separate “Promisified” APIs/modules have been created
e.g., require('fs').promises
“Promisify” asynchronous API ourself using util.promisify()
Creating a Promise
async
/await
(ECMAScript 2017)
async
Function
async function sendPicture (id ) {
...
if (cond) {
return val;
} else {
throw new Error ("Error!" );
}
}
Adding async
to function declaration “promisifies” the function
async
function returns a promise, not val
from return val
If the original function returns a (regular) value, the returned promise is fulfilled to the value.
If the original function throws an error, the returned promise is rejected to the error.
Q: What if the original function returns a promise?
await
Keyword
user = await db.find({userid : id});
await
can be used in front of (a function that returns) a promise
The next “action” is performed after the promise is fulfilled/rejected
If promise is fulfilled, the fulfilled value is returned from await
If promise is rejected, an exception is raised (which can be caught with try/catch
)
await
keyword can be used only inside async
function
async
/await
async function sendPicture (id ) {
try {
user = await db.find({userid : id});
picture = await fs.readFile(user.picFile);
await socket.write(picture);
console .log("done!" );
} catch (e) {
throw new Error ("Cannot send the picture!" );
}
}
async
/await
makes asynchronous program look almost like synchronous program!
await
makes an asynchronous function call “synchronous”
The next line is blocked until the function call is completed
async
converts any function to be “asynchronous”
The call to sendPicture()
is returned immediately with a promise
Best of both worlds!
We can code sendPicture()
like a synchronous program, but the call to sendPicture()
is nonblocking!
await
in Top Block
Q: What if we want to use await
in the outer most block, not in a function?user = await db.find({userid : 'john' });
picture = await fs.readFile(user.picFile);
await socket.write(picture);
console .log("done!" );
await
can be used only in a async
function, but they are not in a function!
A: Wrap the outer most block in an anonymous async
function and call it(async () => {
user = await db.find({userid : 'john' });
picture = await fs.readFile(user.picFile);
await socket.write(picture);
console .log("done!" );
})();
Parallel await
function doubleAfter2Seconds (x ) {
return new Promise ((resolve, reject ) => setTimeout (resolve, 2000 , x*2 ));
}
async function addAsync (x ) {
return await doubleAfter2Seconds(x)
+ await doubleAfter2Seconds(x)
+ await doubleAfter2Seconds(x);
}
addAsync(10 ).then(v => console .log(v));
Q: How long will it take to print out the result?
Three ways to create a promise
Constructor new Promise(func)
Constructor takes one input parameter func
, a function object
func
should a function with two input parameters, resolveCB
and rejectCB
like func(resolveCB, rejectCB)
func
performs whatever operation it needs to do and call resolveCB
with the fulfilled value if successful, or call rejectCB
with error if failed.
Promise.reject(err)
Returns a promise that always rejects with err
Promise.resolve(val)
Returns a promise that is always fulfilled with val
async function addAsync (x ) {
const a = doubleAfter2Seconds(x);
const b = doubleAfter2Seconds(x);
const c = doubleAfter2Seconds(x);
return await a + await b + await c;
}
addAsync(10 ).then(v => console .log(v));
Q: How long will it take?
What We Learned
Single-threading vs Multi-threading
Blocking function calls
Synchronous API vs Asynchronous API
Nested callbacks (a.k.a. callback hell)
Promise (ECMAScript 2015)
async
/await
(ECMAScript 2017)
References