MENU
Practical Asynchrony
Asynchrony is the dealing with events that occur independently of the main program flow. Examples of these events in JavaScript include AJAX calls, file/database access, messaging etc. Apparently, operations that involve I/O completions often warrant asynchronous execution. This is because asynchronous execution prevents the execution from being blocked (forced to pause) when another I/O operation is being carried out and allows the main program to be signaled and properly resumed when the I/O operation has been completed. This improves the computational efficiency and overall user experience.
JavaScript is single-threaded insofar as code interpretation is concerned and no external API is used. Typically, the coder does not have to explicitly manage multiple threads, although a runtime environment may use multiple threads in the background to run a JavaScript program.
Modern browsers allow the coder to spawn threads through Web Workers. In fact, to handle some I/O operations, browsers use their own threads that are separate from the JavaScript thread.
Some advanced built-in web APIs such as navigator.mediaDevices.getUserMedia() (10.5.1) and navigator.permissions.query() (10.6.2) inherently use Promise.
Server-wise, in NodeJS, event demultiplexing (inherent in its single-threaded model) is handled by libuvlibuv libuv ,, , the low-level I/O engine of NodeJS. Additionally, NodeJS uses separate threads to handle asynchronous access to files and databases, as well as certain communications.To spawn threads that can run in parallel on separate cores, we can use the 'cluster' / 'webworker' package. Some of the APIs may be different from those of browsers, but the techniques are more or less similar to those we have seen on the client side.
In short, the new asynchronous coding paradigm often stems from the need to effectively use those asynchronous APIs provided by the programming interface. These APIs, in turn, handle independent events concurrently in the background. To queue our own macrostask/microtask for asynchronous execution, we can use setTimeout() or process.tick().
Sleep Sleep Sleep As demonstrated previously, you can halt script execution momentarily with an async function.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
function sleep(duration){ return new Promise(resolve=>{ setTimeout(resolve, duration); });}(async ()=>{ await sleep(5000); alert("5 seconds passed");})();alert(“wait started.");
</script></body><html>
Background Work while Waiting for Input Background Work while Waiting for Input Background Work while Waiting for Input With Promise, you can run background input/output tasks while prompting for user input.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
function promptForInput(){ return new Promise(resolve=>{ resolve(prompt("Give me a number:")); });}function background_IO_Work(n){ return new Promise(resolve=>{ setTimeout(()=>resolve(n),2000); });}async function start_demo(){ let v = await Promise.all([background_IO_Work(10),
promptForInput()]); alert(v[0]*v[1]);}start_demo();
</script></body><html>
Time Limit Time Limit Time Limit Moreover, we can now impose a time limit on certain operations.
The following program tries to display three images only after all of them have been downloaded completely. This technique may be useful if you want to load many images and you don't want to show them loading one after another in the browser.
Additionally, we also impose a time limit below, stopping all the downloading if it takes too long.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
<!DOCTYPE html><html><head></head><body><script>function loadImage(file){
return new Promise((resolve,reject)=>{
var img = new Image();
img.onload = function(){resolve(img);}
img.onerror= function(){reject(file);}
img.src=file;
});}function timeLimit(seconds){
return new Promise((resolve,reject)=>{
setTimeout(()=>{reject(null);},seconds); });}var images=['image1.png','image2.jpg','image3.png'];Promise.race([
Promise.all(images.map(loadImage)),
timeLimit(10000)]).then(a=>{
for (v of a) document.body.append(v);}, b=>{
document.write('images are too large');});</script></body></html>
</script></body><html>
(Note that for the above demonstration to work, the browsers must not have cached the images before.)
The same can be performed on audio files and fonts.
The example at 9.1.12 shows how to dynamically load a sprites sheet into an array of bitmap promises to create an animation.
AJAX AJAX AJAX In the past, a computation that requires multiple non-blocking calls to be made first would lead to a 'callback hell'. For example, the following program tries to sum up three numbers retrieved from separate URLs. The AJAX calls have to be called one after another, resulting in a deep structure of nested callbacks. Error checking would also have to be done repeatedly.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
<!DOCTYPE html><html><head></head><body><script>function ajax(url,callback){
let xhr=new XMLHttpRequest();
xhr.onreadystatechange=function() {
if (xhr.readyState==4)
if (xhr.status==200) callback(xhr.responseText);
else if (xhr.status==404)
callback("URL inaccessible");
}
xhr.open("GET",url,true);
xhr.send();}ajax('1.txt', function(a){
if (a=="URL inaccessible") {console.log(a); return;}
ajax('2.txt', function(b){
if (b=="URL inaccessible") {console.log(b); return;}
ajax('3.txt', function(c){
if (c=="URL inaccessible") {console.log(c); return;}
console.log(parseInt(a)+ parseInt(b)+ parseInt(c));
});
});});</script></body></html>
</script></body><html>
(Although it is possible to shorten the code above with a recursive AJAX function, sequentially waiting for the AJAX calls to return one-by-one is inherently slow .)
With Promise, AJAX calls can now be carried out simultaneously. You may wish to take note of how to code the AJAX promise function, which is pretty standard.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
<!DOCTYPE html><html><head></head><body><script>function ajax(url, attempts_left=3){ if (attempts_left==0)
Promise.reject("AJAX attempted 3 times"); return new Promise((resolve,reject)=>{ let xhr=new XMLHttpRequest(); xhr.onreadystatechange=function() { if (xhr.readyState==4){
if (xhr.status==200)
resolve(parseInt(xhr.responseText));
else if (xhr.status==404)
reject("URL inaccessible");
else
ajax(url, attempts_left-1).then(resolve,reject);
} } xhr.open("GET",url,true); xhr.send(); });} Promise.all([ajax('1.txt'),ajax('2.txt'),ajax('3.txt')]) .then(a=>{console.log(a.reduce((x,y)=>(x+y)));}, e=>{console.log("Oops! "+e);});</script></body></html>
</script></body><html>
Cross Messaging Cross Messaging Cross Messaging If you ever spawn threads to be run on separate cores, you can now also invoke a callback immediately after all the threads have finished running.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
// work.jsonmessage = n=>{
postMessage('abcdefghijklmnopqrstwxyz'[
Math.floor(Math.random()*26)]);}
</script></body><html>
<!DOCTYPE html><html><body><script>
<!DOCTYPE html><html><head></head><body><script>function DoWork(n){
return new Promise((resolve,reject)=>{
var w = new Worker('work.js');
w.onmessage = e=>resolve(e.data);
w.postMessage(n);
});}Promise.all(Array(16).fill(0).map(i=>DoWork(i))) // 16 threads.then(a=>console.log(a.join(''))); // gidplsojbfstoqpt</script></body></html>
</script></body><html>
Programmatic Script Loading Programmatic Script Loading Programmatic Script Loading
Sometimes the order in which external scripts are loaded is important, as some scripts may need to be loaded first in order for subsequent dependent scripts to run correctly. This is especially true for hybrid apps (eg. by Cordova) where you may choose to load many scripts from a server dynamically in order to minimize the storage footprint of the app, and the loading speed is crucial.
RESETRUNFULL
<!DOCTYPE html><html><body><script>
<!DOCTYPE html><html><head><script>function loadScript(url){
var s;
return new Promise((resolve,reject)=>{
s = document.createElement('script');
s.type = 'text/javascript';
s.src = url;
s.onload = resolve;
document.head.appendChild(s);
}).catch(e=>{s.remove();loadScript(url);});}async function load_all_scripts(){
await Promise.all(['a.js','b.js','c.js'].map(loadScript));
await Promise.all(['d.js','e.js','f.js'].map(loadScript));
await Promise.all(['g.js','h.js','i.js'].map(loadScript));}load_all_scripts();</script></head><body></body></html>
</script></body><html>
Asset bundlers such as Webpack, Parcel, and Browserify(12.2) inherently manage script dependencies, by analyzing ES6 imports and exports for instance.