1 JavaScript Concurrent Programming
2 Making Promises
3 Handling promises using methods
4 Handling promises with async/await construct
A promise is an action that produces a result at some point in the future, unless it dies with an exception.
To combine two promises, you need to use method calls.
Instead, the async/await constructs give you a simpler syntax that has the ordinary sequential flow.
1 JavaScript Concurrent Programming
What is a concurrent program?
- a program is concurrent if it controls multiple activities which execute at same time, with overlapping timelines
What is I/O operations?
- I/O (input/output) operations are also known as time-consuming operation.
- I/O operaions occurr when the program interacts with the system’s disk and network.
- I/O operations include reading/writing data from/to a disk, making HTTP requests and querying databases.
Concurrent programs in Java and C++
- these programming languages are multithreaded
- a program has a main thread, time-consuming operations are handled in a separate thread of execution
- if multiple threads modify shared data, access to data must be regulated to prevent data corruption
Concurrent programs in JavaScript
- JavaScript programs run in a single thread. If a function starts it will have to complete its execution, before another function is able to start
- if a JavaScript program has to wait for something to happen, it cannot do something else in another thread
- if a function modifies some shared data, other functions cannot access this data before the function returns.
This means that in your javascript programs you don’t have to worry about synchronizations and mutex locks.
How JavaScript process time-consuming operations?
- JavaScript has three features that allow you to run code in concurrently: callbacks, promises and async/await construct
- time-consuming operations in JavaScript are asynchronous, also known as non-blocking
- for example a program that is waiting for a HTTP response specifies a callback function; the current function continues execution and the callback is invoked when data is available
Example: a function that loads a JSON object from a given url
const loadJSON = url => {
const request = new XMLHttpRequest();
request.addEventListener('load', (e) => {
if (request.status === 200) { // status code for success
let json = JSON.parse(request.responseText);
console.log({json})
} else {
console.error(`Server responded with a ${request.status}`)
}
});
request.addEventListener('error', (e) => console.error("NetworkError"));
request.addEventListener('timeout', (e) => console.error("Timeout!!"));
request.open('GET', url, true);
request.send();
};
Note to the example:
- the function uses the
XMLHttpRequestAPI - the response's payload is processed sometime later in the asynchronous callback
- an invocation to the loadJSON function returns immediately and does not wait for the data to load
Callbacks inside callbacks or Callbacks Hell
- Doing tasks in order requires callbacks inside callbacks
- Consider a situation where your program loads a page which contains the URL of an image and then it loads the image as well: this leads to nested calls
Promises VS Callbacks
- promises allow to combine asynchronous tasks without nesting callbacks
- promises make it easy to link completion and error actions than callbacks
2 Making Promises
How to build promises
- you can make promises either by invoking APIs that yield promises or by invoking the Promise constructor
- note: it is more likely that you use libraries that return promises, than you invoke the Promise constructor for making your own promises
Example: a fetch call yields a promise that resolves with a Response object
// fetch JSON for client IP address
let promise = fetch('https://api.ipify.org?format=jsonp&callback=?');
console.log(promise); // Promise {<pending>}
// fetch image file
promise = fetch('https://upload.wikimedia.org/wikipedia/commons/d/d9/Wikipedia_Monument_2.JPG');
console.log(promise); // Promise {<pending>}
Building promises invoking the Promise constructor, a summary of the process
const myPromise = new Promise((resolve, reject) => {
const callback = (args) => {
if (success) resolve(result)
else reject(reason)
}
asynTask(callback)
})
- the
Promiseconstructor takes as its argument a function, called executor function - the executor function takes two parameters, which are handler functions
- In the body of the executor function you start the async task:
- if the task yields a result, the result is passed to the
resolvehandler - if the task fails, the
rejecthandler is invoked passing the reason of the failure
Example: the promiseSum promise factory function
// A factory that yields a promise that resolves with the sum two given numbers
function promiseSum(add1, add2, delay = 1000) {
return new Promise((resolve, reject) => {
const callback = () => {
let sum = add1+add2;
resolve(sum);
}
setTimeout(callback, delay);
})
}
Note to the example
- the executor function invokes
setTimeoutpassing a callback and a delay.
After the delay has passed, the callback is invoked, which passes the result to theresolvehandler
Promise Control Flow
- the life cycle of a js promise: from the promise's creation to the promise's result production
- The
Promise()constructor is called.- the JavaScript interpreter invokes the constructor argument:
the executor function is called, being passed
resolveandreject - the executor function set up the asynchronous task and returns
- the constructor returns.
- at this point, the promise is in the pending state.
- the JavaScript interpreter invokes the constructor argument:
the executor function is called, being passed
- some time passes ...
- the async task completes and invokes a callback that you provided in the executor function
- if in that callback you have a call to
rejecthandler, the promise is rejected and settled - if in the callback you have a call to
resolvehandler with an object other than a promise, then the promise is fulfilled and settled - if in the callback you have a call to
resolvewith another promise, then the current promise is resolved but not fulfilled
3 Handling promises using methods
The Promise.prototype.then(onFulfilled) method allows to define a fulfillment handler
promise.then(value => {…})
- the fulfillment handler processes the result of the promise
- the fulfillment handler is to be executed when the promise is resolved
Example: get the result of the delayed sum of two numbers
function promiseSum(add1, add2, delay = 1000) {
return new Promise((resolve, reject) => {
const callback = () => {
let sum = add1+add2;
resolve(sum);
}
setTimeout(callback, delay);
})
}
let promise = promiseSum(25, 25);
console.log(promise); // Promise {<pending>}
// in then method, get the sum, result of the promise
promise.then((value) => {
console.log(promise); // Promise { 50 }
console.log("Sum is: ", value); // Sum is: 50
});
Example: get the Response, result of fetch invocation
// fetch makes a promise that resolves with a Response object
let promise = fetch('https://api.ipify.org?format=json');
console.log(promise); // Promise {<pending>}
// in then method, get Request object, result of fetch promise
promise.then(response => {
console.log(promise); // Promise {<fulfilled>: Response}
console.log(response) ; // Response {type: "cors", url: "https://api.ipify.org/?format=json", redirected: false, status: 200, ok: true, …}
});
The Promise.prototype.then method allows to chain promises
- As already said, ECMAScript 6 introduced promises to make it easier to do one asynchronous task and then another:
you can use thethenmethod to pass the result of a promise to another promise - after a Promise is fulfilled, the then method is called;
suppose that the then's handler produces another promise:
to process the result of the second promise, invoke the then method once again
Example: get the result of response.json() by invoking then method
// fetch() returns a promise and response.json() returns a promise as well
fetch('https://api.ipify.org?format=json')
.then( response => { // first then invocation
console.log(response) ; // Response { … }
return response.json();
})
.then( json => { // append a second then invocation
console.log(json); // {ip: "83.47.216.44"}
document.getElementById('text').textContent = `IPv4: ${json.ip}`;
})
The Promise.prototype.catch(onRejected) method allows to define a rejection handler
promise.catch(reason => {…})
- the rejection handler is called if a rejection has happened.
- the catch method yields a new promise, the same way the then method yields a new promise.
- the result of the new promise depends on the return value of the rejection handler:
- if it is a value, the promise is now fulfilled with that value;
if it throws another exception, the promise is still rejected with a given reason;
if it returns another promise, that promise is chained - commonly, the rejection handler deals with the problem and then returns a value to resolve the promise
The Promise.prototype.finally(onFinally) method invokes the onFinally handler
after the promise is settled
promise.finally(() => {…})
- the finally handler is always invoked, no matter the promise is resolved or rejected
- the finally handler has the purpose of doing some clean up that has to occur no matter what happend before
- the handler has no arguments
Example: use catch and finally methods with fetch API
const update = () => {
// get JSON for client IP
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(json => {
// update DOM to show IP
document.querySelector('section#result').innerHTML=
`<h2>My Public IP</h2>
<p class="ip"> ${json.ip} </p>
<p> Last update: ${(new Date()).toLocaleTimeString()}</p>`;
})
.finally(() => {
// always update DOM to add the reload button
document.querySelector('section#reload').innerHTML =
`<a id="refresh-icon" href="#" title="refresh"><i class="fas fa-sync-alt"></i></a>`
document.getElementById('refresh-icon').addEventListener('click', e => {
e.preventDefault();
update();
});
})
.catch(err => {
// update DOM with error message
console.error(err);
document.querySelector('section#result').textContent='Sorry, we cannot load data';
})
}
document.addEventListener('DOMContentLoaded', update);
See the Pen fetch then finally catch by Massimiliano De Simone (@maxdesimone) on CodePen.
4 Handling promises with async/await construct
Which programming style to use with promises?
- the asynchronous programming style
- a chain of
then/finally/catchmethod invocations - code hard to read and reason about
- the synchronous programming style
-
awaitoperator withinasyncfunctions - code with sequence of statements and using the traditional control flow
The await operator
let value = await promise
- the value of the
awaitoperator is the fulfillment value of the promise object - it makes your program appear as it were waiting for the promise to settle
- it can only occur within a function tagged by
asynckeyword
Example: async function with await fetch
const loadJSON = async (url) => {
// await gets fetch promise result
const response = await fetch(url);
// await gets response.json promise result
const json = await response.json();
}
Async functions
- the JavaScript interpreter rewrites async functions: code after
awaitis put into a then handler and executed after the promise resolves - You can apply the async keyword to different kinds of functions: function expressions and function definitions, arrow functions, class methods and object literal methods
- the result of the
asynckeyword is a function object which is an instance of theAsyncFunctionclass
Example: async functions sequential programming style
const update = async () => {
try {
// remove refresh and add spinner icon
document.querySelector('section#spinner').innerHTML =
`<a id="refresh-icon" href="#" title="refresh">
<i class="fas fa-sync-alt fa-spin"></i></a>`;
// await fetch() evaluates to http response
const response = await fetch('https://api.ipify.org?format=json');
// await response.json() evaluates to json
const json = await response.json();
// show result to user
document.querySelector('section#result').innerHTML=
`<h2> My Public IP </h2>
<p class="ip"> ${json.ip} </p>
<p> Last update: ${(new Date()).toLocaleTimeString()} </p>`;
} catch (err) {
// show error message to user
document.querySelector('section#result').textContent='Sorry, we cannot load data';
console.error(err);
} finally {
// always remove spinner & add refresh icon
document.querySelector('section#spinner').innerHTML =
`<a id="refresh-icon" href="#" title="refresh">
<i class="fas fa-sync-alt"></i></a>`;
document.getElementById('refresh-icon').addEventListener('click', e => {
e.preventDefault();
update();
});
} // END finally
} // END try
document.addEventListener('DOMContentLoaded', update);
See the Pen async function by Massimiliano De Simone (@maxdesimone) on CodePen.
Async functions return values
- async functions always return a promise
- if the function returns a value, the value becomes a promise
- if the function returns
null, it actually returnsPromise.resolve(null)
Example: an async function returning a value
// the function returns a string value, but the value becomes a promise
const getClientIP = async () => {
const response = await fetch('https://api.ipify.org?format=json');
const json = await response.json();
return json.ip; // returns a promise
}
// To get a value returned by async functions invocations:
// - either invoke the async function and chain a then method
getClientIP().then( value => console.log(value) )
// - or invoke the async function from within another async function
const clientFunction = async () => {
const value = await getClientIP();
}
clientFunction();
Example: async function return null
const loadJSON = async (url) => {
// return null becomes Promise.resolve(null)
if (url === undefined) return null;
// await gets fetch promise result
const response = await fetch(url);
// await gets response.json promise result
const json = await response.json();
}
What if an async function throws an exception?
- if a statement of an async function throws an exception, the async function yields a rejected promise
What if an await get a rejected promise?
- if await operator gets a rejected promise, it throws an exception
Handling errors by async function invocations
- when using await, you should set up a strategy for error handling
- for example, the top level async function encloses all async function invocations within a try/catch statement
Example: async function that does exception handling
See the Pen async functions exception handling by Massimiliano De Simone (@maxdesimone) on CodePen.

No comments:
Post a Comment