Home [JavaScript for Alexa Skill Development] Chapter 13: Asynchronous features
Post
Cancel

[JavaScript for Alexa Skill Development] Chapter 13: Asynchronous features

Good reference, this Blog.

Contents from this book JavaScript: The Definitive Guide, 7th Edition channel.

Promises are objects that represent the not-yet-available result of an asynchronous operation. The keywords async and await simplify asynchronous programming by allowing you to structure your Promise-based code as if it was synchronous. Finally, the for/await loop allow you to work with streams of asynchronous events using simple loops that appear synchronous.

1. Asynchronous Progamming with Callbacks

A callback is a function that you write and then pass to some other function. That other function then invokes your function when some condition is met or some synchronous event occurs.

Timers

1
setTimeout(checkForUpdates, 60000);

Events

Client-side JavaScript programs are almost universally event driven: rather than running some kind of predetermined computation, they typically wait for the user to do something and then respond to the user’s actions.

1
2
3
4
let okay = document.querySelector('#confirmUpdateDialogbutton.okay');

// Now register a callback function to be invoked when the user clicks on that button.
okay.addEventListener('click', applyUpdate);

Network Events

Another common source of asynchrony in JavaScript programming is network requests.

network callback example

getCurrentVersionNumber() makes an asynchronous request, it cannot synchronously return the value that the caller is interested in. Instead, the caller passes a callback function, which is invoked when the result is ready or when an error occurs.

Callbacks and Events in Node

Node.js asynchronous example: reading the contents of a file. fs.readFile() takes a two-parameter callback as its last argument.

network callback example

Example to request for the contents of a URL in Node. Node uses on() method to register event listeners instead of addEventListener().

network callback example network callback example

2. Promises

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

A Promise is an object that represents the result of an asynchronous computation. That result may or may not be ready yet, and the Promise API is intentionally vague about this: there is no way to synchronously get the value of a Promise; you can only ask the Promise to call a callback function when the value is ready.

To make it Promise-based, omit the callback argument, and instead return a Promise object. The caller can then register one or more callbacks on this Promise object, and they will be invoked when the asynchronous computation is done.

callbacks drawbacks:

  • nested callback, not clean
  • difficult error handlings

Using Promises

The function you pass to then() is invoked asynchronously, even if the asynchronous computation is already complete when you call then().

1
2
3
4
getJSON(url).then(jsonData => {
    // This is a callback function that will be asynchronously
    // invoked with the parsed JSON value when it becomes available.
});

promise example

Handling Errors with Promises
Callback vs. Promise
When something goes wrong in a synchronous computation, it throws an exception that propagates up the call stack until there is a catch clause to handle it. When an asynchronous computation runs, its caller is no longer on the stack, so if something goes wrong, it is simply not possible to throw an exception back to the caller.

Asynchronous operations can fail in a number of ways, and robust code has to be written to handle the errors that will inevitably occur. When a Promise-based asynchronous computation completes normally, it passes its result to the function that is the first argument to then(). For Promises, we can do this by passing a second function to the then() method for error handling.

1
getJSON("/api/user/profile").then(displayUserProfile, handleProfileError);

promise example

Promise Terminology we say that the Promise has been fulfilled if and when the first callback is called; we say that the Promise has been rejected if and when the second callback is called; if a Promise is neither fulfilled nor rejected, then it is pending. And once a promise is fulfilled or rejected, we say that it is settled.

Chaining Promises

Promise provides a natural way to express a sequence of asynchronous operations as a linear chain of then() method invocations, without having to nest each operation within the callback of the previous one.

Example to illustrate how a chain of Promises can make it easy to express a sequence of asynchronous operations

promise example

Method chain example:

1
fetch().then().then()

We know that the fetch() function returns a Promise object, and we can see that the first .then() in this chain invokes a method on that returned Promise object. But there is a second .then() in the chain, which means that the first invocation of the then() method must itself return a Promise.

1
2
3
fetch(theURL)           // task 1; returns promise 1
    .then(callback1)    // task 2; returns promise 2
    .then(callback2);   // task 3; returns promise 3

But just because task 2 begins when c1 is invoked, it does not mean that task 2 must end when c1 returns. Promises are about managing asynchronous tasks, after all, and if task 2 is asynchronous (which it is, in this case), then that task will not be complete by the time the callback returns.

promise example

About resolved:

promise example

Promises in Parallel

Sometimes we want to execute a number of asynchronous operations on parallel.

Making Promises

This section shows how you can create your own Promise-based APIs.

Promises Based On Other Promises

Given a Promise, you can always create a new one by calling .then().

1
2
3
4
//Promise returned by getJSON() resolves to the Promise returned by response.json(). 
function getJSON(url) {
    return fetch(url).then(response => response.json());
}

Promises Based On Synchronous Values

Sometimes, you may need to implement an existing Promise-based API and return a Promise from a function, even though the computation to be performed does not actually require any asynchronous operations.

Promise.resolve() and Promise.reject()

Promise.resolve() takes a value as its single argument and returns a Promise that will immediately(but asynchronously) be fulfilled to that value.

To be clear:
The Promises returned by these static methods are not already fulfilled or rejected when they are returned, but they will fulfill or reject immediately after the current synchronous chunk of code has finished running. Typically, this happens within a few milliseconds unless there are many pending asynchronous tasks waiting to run.

It is fairly common to have synchronous special cases within an asynchronous function, and you can handle these special cases with Promise.resolve() and Promise.reject().

Promises from Scratch

what about writing a Promise-returning function when you can’t use another Promise-returning function as the starting point? In that case, you use the Promise() constructor to create a new Promise object that you have complete control over.

Alexa skill example:

promise example

Pay attension:

promise example

The constructor synchronously calls your function with function arguments for the resolve and reject parameters. After calling your function, the Promise() constructors returns the newly created Promise. That returned Promise is under the control of the function you passed to the contructor.

That function should perform some asynchronous operation and then call the resolve function to resolve or fulfill the returned Promise or call the reject function to reject the returned Promise.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function wait(duration) {
    // Create and return a new Promise
    return new Promise((resolve, reject) => {   // These control the Promise
                                                // If the argument is invalid, reject the Promise
        if (duration < 0) {
            reject(new Error("Time travel not yet implemented"));
        }

        // Otherwise, wait asynchronously and then resolve the Promise.
        // setTimeout will invoke resolve() with no arguments, which means
        // that the Promise will fulfill with the undefined value.
        setTimeout(resolve, duration);
    });
}

This getJSON() example illustrates how we can implement Promise-based APIs on top of other styles of asynchronous programming.

promise example promise example

Promises in Sequence

3. async and await

These new keywords dramatically simplify the use of Promises and allow us to write Promise-based, asynchronous code that look like synchronous code that blocks while waiting for network response or other asynchronous events.

await Expressions

The await keyword takes a Promise and turns it back into a return value or a thrown exception. Given a Promise object p, the await p waits until p settels. If p fulfills, then the value of await p is the fulfillment value of p. If p is rejected, then the await p throws the rejection value of p.

It literally does nothing until the specified Promise settles. The code remains asynchronous, and the await simply disguises this fact. This means that any code that uses await is itself asynchronous.

1
2
let response = await fetch("/api/user/profile");
let profile = await response.json();

async Functions

Because any code that uses await is asynchronous, there is one critical rule: you can only use the await keyword within functions that have been declared with the async keyword.

Declaring a function async means that the return value of the function will be a Promise even if no Promise-related code appears in the body of the function.

1
2
3
4
5
async function getHighScore() {
    let response = await fetch("/api/user/profile");
    let profile = await response.json();
    return profile.highScore;
}

The getHighScore() function is declared async, so it returns a Promise. And because it returns a Promise, we can use the await keyword with it:

1
displayHighScore(await getHighScore());

Awaiting Multiple Promises

Given this getJSON() function:

1
2
3
4
5
async function getJSON(url) {
    let response = await fetch(url);
    let body = await response.json();
    return body;
}

Now suppose we want to fetch two JSON values with this function:

1
2
let value1 = await getJSON(url1);
let value2 = await getJSON(url2);

Issue:
It is unnecessarily sequential: the fetch of the second URL will not begin until the first fetch is complete. If the second URL does not depend on the value obtained from the first URL, then we should probably try to fetch the two values at the same time.

Solution:
In order to await a set of concurrently executing async functions, we use Promise.all() just as we would if working with Promises directly:

1
let [value1, value2] = await Promise.all([getJSON(url1), getJSON(url2)]);

Implementation details

To think about what is going on under the hood. Suppose you write an async like this:

1
async function f(x) { /* body */ }

You can think of this as a Promise-returning function wrapped around the body of your original function:

1
2
3
4
5
6
7
8
9
10
function f(x) {
    return new Promise(function(resolve, reject) {
        try {
            resolve((function(x) { /* body */ })(x));
        }
        catch(e) {
            reject(e);
        }
    });
}

4. Asynchronous iteration

Promises are useful for single-shot asynchronous computations but were not suitable for use with resources of repetitive asynchronous events, such as setInterval(), the “click” event in a web browser, or the “data” event on a Node stream. Because single Promises do not work for sequences of asynchronous events, we also cannot use regular async functions and the await statements for these things.

The for/await Loop

This post is licensed under CC BY 4.0 by the author.