Dr. Greg Bernstein
Updated March 18th, 2021
event queue and callback functions
Promises refines the above in a standard way with a number of benefits. They are incorporated in a number of standard APIs.
Async/Await Functions in ES2017 build on Promises
From Promises/A+
A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.
new Promise(function(resolve, reject) { ... } );
resolve
: a callback function that is called if the promise resolves successfully.reject
: a callback function that is called if the promise is rejected.In fact can be in three states:
SimplestPromise.js
: Try on the console or with Node.js.
myP = new Promise(function(resolve, reject){ // Trivial promise
resolve("Hi Web Systems!");
});
function sucessHandler(msg) { // If things go well
console.log(msg);
}
function rejectHandler() { // If things don't go well
console.log("It was rejected!");
}
myP.then(sucessHandler, rejectHandler); // See what happens...
.then()
p.then(onFulfilled[, onRejected]);
onFulfilled: A Function called when the Promise is fulfilled. This function has one argument, the fulfillment value.
onRejected (Optional): A Function called when the Promise is rejected. This function has one argument, the rejection reason.
Wait for it… timePromise.js
myP = new Promise(function(resolve, reject){ // Trivial promise
setTimeout(()=>resolve("Hi Web Systems!"), 5000);
});
function sucessHandler(msg) { // If things go well
console.log(msg);
}
myP.then(sucessHandler);
console.log("I was called after myP.then ...");
rejectPromise.js
: Try on the console or with Node.js.
myP = new Promise(function(resolve, reject){ // Trivial promise
setTimeout(()=>reject("Something bad happened!"), 2000);
});
function sucessHandler() { // If things go well
console.log("Things are Great!");
}
function rejectHandler(msg) { // If things don't go well
console.log(msg);
}
console.log("Trying and getting rejected!");
myP.then(sucessHandler, rejectHandler); // See what happens...
multipleListeners.js
: Try on the console or with Node.js.
myP = new Promise(function(resolve, reject){
setTimeout(()=>resolve("Hi Web Systems!"), 2000);
});
myP.then(function(msg) {console.log("listener 1: " + msg)})
myP.then(function(msg) {console.log("listener 2: " + msg)})
console.log("Called after myP.then ...");
An alternative for listening for rejection. From catch()
The catch()
method returns a Promise and deals with rejected cases only. It behaves the same as calling Promise.prototype.then(undefined, onRejected)
.
myP = new Promise(function(resolve, reject){
setTimeout(()=>reject("Something Bad :-<"), 2000);
});
myP.then(function(msg) {console.log("Doing Great!")})
.catch(function(msg) {console.log(msg)});
console.log("Called after myP.then and myP.catch ...");
lateParty.js
: Try checking the promise way after it should have been resolved:
myP = new Promise(function(resolve, reject){
setTimeout(()=>resolve("Hi Websystems!"), 100); // 0.1 second!
});
// These will check on the promise much later...
setTimeout(() => myP.then(function(msg) {console.log("listener 1: " + msg)}), 3000);
setTimeout(() => myP.then(function(msg) {console.log("listener 2: " + msg)}), 6000);
We can modify promises and return them modifyPromise.js
:
myP = new Promise(function(resolve, reject){
setTimeout(()=>resolve("Hi Web Systems!"), 3000);
});
// What is myP2?
myP2 = myP.then(function(msg) {return "I saw: " + msg;});
console.log("Is myP2 a Promise? " + (myP2 instanceof Promise));
myP2.then(function(msg) {console.log(msg)});
We can listen to promises at multiple places.
We can never miss a promise
We can return modified promises (a form of process chaining)
Let’s see how we can use promises to help us manage asynchronous execution
badTimes.js
What will this do?
myTime = 0.0;
startTime = new Date();
function advanceTime() {
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
}
// What will this do?
setTimeout(advanceTime, 1000);
setTimeout(advanceTime, 1000);
setTimeout(advanceTime, 1000);
setTimeout(advanceTime, 1000);
Not really a good clock by any measure…
$ node badTimes.js
myTime = 1, elapsedTime = 1.001
myTime = 2, elapsedTime = 1.003
myTime = 3, elapsedTime = 1.003
myTime = 4, elapsedTime = 1.004
goodTimes.js
: Nest those calls!
myTime = 0.0;
startTime = new Date();
// What will this do?
setTimeout(function(){
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
setTimeout(function(){
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
setTimeout(function(){
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
setTimeout(function(){
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
It counts! But not pretty
$ node goodTimes.js
myTime = 1, elapsedTime = 1.001
myTime = 2, elapsedTime = 2.005
myTime = 3, elapsedTime = 3.009
myTime = 4, elapsedTime = 4.01
Let’s try throwing around a promise goodTimePromise.js
myTime = 0.0;
startTime = new Date();
function oneSecond() { // Returns a promise that resolves in one second
return new Promise(function(resolve, reject){
setTimeout(()=>resolve(), 1000);
});
}
function advanceTime() {
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
return oneSecond(); // Returns another new one second promise
}
// What will this do?
oneSecond()
.then(advanceTime)
.then(advanceTime)
.then(advanceTime);
Let’s try throwing around a promise goodTimePromise.js
myTime = 0.0;
startTime = new Date();
function oneSecond() { // Returns a promise that resolves in one second
return new Promise(function(resolve, reject){
setTimeout(()=>resolve(), 1000);
});
}
function advanceTime() {
myTime += 1.0;
elapsedTime = (new Date() - startTime)/1000.0;
console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
return oneSecond(); // Returns another new one second promise
}
// What will this do?
let p1 = oneSecond();
let p2 = p1.then(advanceTime);
let p3 = p2.then(advanceTime);
$ node goodTimePromise.js
myTime = 1, elapsedTime = 1.003
myTime = 2, elapsedTime = 2.01
myTime = 3, elapsedTime = 3.01
From Promise.all():
The Promise.all()
method returns a single Promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first promise that rejects.
Promise.all(iterable);
allPromise.js
myP1 = new Promise(function(resolve, reject){
setTimeout(()=>resolve("Hi from P1!"), 1000);
});
myP2 = new Promise(function(resolve, reject){
setTimeout(()=>resolve("Hi from P2!"), 5000);
});
myP3 = new Promise(function(resolve, reject){
setTimeout(()=>resolve("Hi from P3!"), 2000);
});
myPs = [myP1, myP2, myP3];
myP1.then((msg) => console.log(msg));
Promise.all(myPs).then((msg) => console.log(msg));
From MDN race
The Promise.race(iterable) method returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.
Browser Example
myP1 = new Promise(function(resolve, reject){
setTimeout(()=>resolve("P1"), 1000);
});
myP2 = new Promise(function(resolve, reject){
setTimeout(()=>resolve("P2"), 5000);
});
myP3 = new Promise(function(resolve, reject){
setTimeout(()=>resolve("P3"), 2000);
});
myPs = [myP1, myP2, myP3];
Promise.race(myPs).then((msg) => console.log(`the winner is ${msg}`));
Node.js achieves good performance by using an event driven model for file system and network operations.
This requires the use of callback functions, e.g., see DNS and File System API documentation.
Wouldn’t it be nice to have a Promise based version of these API’s to simplify our code?
util.promisify(original)
From util.promisify(original) documentation
util.promisify(original)
<Function>
, Returns: <Function>
Takes a function following the common error-first callback style, i.e. taking an (err, value) => … callback as the last argument, and returns a version that returns promises.
nodeFileCat2.js
const fs = require('fs');
const dirRoot = __dirname + "/sample_files/";
let myString = " Empty\n";
// Nest callbacks to gurantee ordering
fs.readFile(dirRoot+"samp1.txt", 'utf8', function(err, data){
if (err) throw err;
myString = data + "\n";
fs.readFile(dirRoot+"samp2.txt", 'utf8', function(err, data){
if (err) throw err;
myString += data + "\n";
fs.readFile(dirRoot+"samp3.txt", 'utf8', function(err, data){
if (err) throw err;
myString += data + "\n";
console.log(myString); // Executes after all files have been read in order
});
});
});
nodeFileCatPromise.js
const fs = require("fs");
const util = require("util");
const dirRoot = __dirname + "/sample_files/";
let myString = " Empty\n";
const readFP = util.promisify(fs.readFile);
// Chaining Promises to guarantee ordering
readFP(dirRoot + "samp1.txt", "utf8")
.then(function(data) {
myString = data + "\n";
return readFP(dirRoot + "samp2.txt", "utf8");
})
.then(function(data) {
myString += data + "\n";
return readFP(dirRoot + "samp3.txt", "utf8");
})
.then(function(data) {
myString += data + "\n";
console.log(myString);
})
.catch(function(err) {
console.log("Some type of file error!");
});
nodeFileCatAsync.js
const fs = require("fs");
const util = require("util");
const dirRoot = __dirname + "/sample_files/";
let myString = " Empty\n";
const readFP = util.promisify(fs.readFile);
// Using await to guarantee ordering.
async function fileCombine() {
try {
myString += await readFP(dirRoot + "samp1.txt", "utf8") + "\n";
myString += await readFP(dirRoot + "samp2.txt", "utf8") + "\n";
myString += await readFP(dirRoot + "samp3.txt", "utf8") + "\n";
console.log(myString);
} catch(e) {
console.log("Some type of file error!");
}
}
fileCombine();