Promise Chaining
JavaScript Promise Chaining
Let’s suppose that you have a sequence of asynchronous tasks to carry out one after another. For example, loading scripts. (假设您有一系列异步任务要一个接一个地执行。例如,加载脚本。)
The JavaScript promises to introduce a couple of receipts to perform that. This chapter is dedicated to promise chaining, which is demonstrated in the example below:
new Promise(function (resolve, reject) {
setTimeout(() => resolve(2), 1500); // (*)
}).then(function (result) { // (**)
console.log(result); // 2
return result + 2;
}).then(function (result) { // (***)
console.log(result); // 4
return result + 2;
}).then(function (result) {
console.log(result); // 6
return result + 2;
});
The notion is that the result passes through the chain of .then handlers. (这个概念是结果通过.then处理程序的链。)
The flow is as follows:
The first promise will resolve in a second () After that, the .then handler is invoked (**) The value it returns is passed to the further .then handler, and so on. (第一个承诺会在一秒钟内得到解决() 之后,调用.then处理程序(* *) 它返回的值将传递给进一步的.then处理程序,依此类推。)
You can notice that the result is passed along the unique chain of handlers, and there is an order of the following alert calls: 2 → 4 → 6.
The whole chain works as a call to promise.then returns a promise in a way that it allows you to call the upcoming .then on it. (整个链的工作原理是对promise.then的调用,它以允许您在其上调用即将到来的.then的方式返回一个promise。)
Whenever a handler returns a value, it is the result of the promise, and the upcoming then should be called with it. (每当处理程序返回一个值时,它就是promise的结果,应该使用它调用即将到来的then。)
Please remember that adding many .then to a promise does not yet mean that it’s chaining. (请记住,在promise中添加许多.then并不意味着它正在链接。)
For instance:
let promise = new Promise(function (resolve, reject) {
setTimeout(() => resolve(2), 1500);
});
promise.then(function (result) {
console.log(result); // 2
return result + 2;
});
promise.then(function (result) {
console.log(result); // 2
return result + 2;
});
promise.then(function (result) {
console.log(result); // 2
return result + 2;
});
In this example, there are just several handlers to a promise. They are not passing the result to each other yet process it separately. (在此示例中, promise只有几个处理程序。他们没有将结果传递给对方,而是单独处理。)
So, the overall .then on the same promise get the same result: that promise’s result. So the console.log in this code shows the same 2.
In practice, developers need this rarely, compared with chaining, which is a quite common practice. (在实践中,与链接相比,开发人员很少需要这个,这是一种非常普遍的做法。)
Returning Promises
Returning Promises (返回承诺)
A handler that is applied for .then(handler) is capable of creating and returning a promise. (应用于.then (处理程序)的处理程序能够创建和返回Promise。)
In such a situation, the next handlers wait until it settles, getting its result after that. (在这种情况下,下一个处理程序等待它解决,然后获得结果。)
It is visualized in the example below:
new Promise(function (resolve, reject) {
setTimeout(() => resolve(2), 1500);
}).then(function (result) {
console.log(result); // 2
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result + 2), 1500);
});
}).then(function (result) { // (**)
console.log(result); // 4
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result + 2), 1500);
});
}).then(function (result) {
console.log(result); // 6
});
In this example, the initial .then shows 2, returning new Promise(…) in the line (*). After just a second, it resolves; after that, the result passes on to the handler of the second .then. That handles is placed on the line (**) and demonstrates 4, doing the same thing.
Its output will be equivalent to the previous example: 2 → 4 → 6. The difference is that now there is a second delay between the console.log calls.
Returning the promises allows building chains of asynchronous actions. (返回Promise允许构建异步操作链。)
Example: loadScript
Example: loadScript
In this part, we are going to use this feature in the promisified loadScript, described in the chapter Promises, applied for loading scripts one by one, in an order:
loadScript("/promiseChaining/oneScript.js")
.then(function (script) {
return loadScript("/promiseChaining/twoScript.js");
})
.then(function (script) {
return loadScript("/promiseChaining/threeScript.js");
})
.then(function (script) {
// use the functions declared in the scripts
(//使用脚本中声明的函数)
// to show that they are really loaded
(//以显示它们确实已加载)
oneScript();
twoScript();
threeScript();
});
The usage of arrow functions can make this code shorter, like here:
loadScript("/promiseChaining/oneScript.js")
.then(script => loadScript("/promiseChaining/twoScript.js"))
(.then (script = > loadScript ("/promiseChaining/twoScript.js")))
.then(script => loadScript("/promiseChaining/threeScript.js"))
(.then (script = > loadScript ("/promiseChaining/threeScript.js")))
.then(script => {
// scripts are loaded, we can use some functions declared there
(//脚本已加载,我们可以使用其中声明的一些函数)
oneScript();
twoScript();
threeScript();
});
In the example above, every loadScript returns a promise, and the upcoming .then runs at the time it resolves. After that, it initiates the next script’s loading. So, we can assume that scripts run in a sequence. (在上面的示例中,每个loadScript返回一个promise ,即将到来的.then在解析时运行。之后,它将启动下一个脚本的加载。因此,我们可以假设脚本按顺序运行。)
It is possible to add more asynchronous action to the chain. Another fundamental thing for keeping in mind is that the code goes down, not to the right. No signs of the “pyramid of doom” exist here. (可以向链中添加更多异步操作。另一件需要记住的基本事情是代码会向下,而不是向右。这里没有“厄运金字塔”的迹象。)
You can add .then to every loadScript, as follows;
loadScript("/promiseChaining/oneScript.js").then(script1 => {
loadScript("/promiseChaining/twoScript.js").then(script2 => {
loadScript("/promiseChaining/threeScript.js").then(script3 => {
// this function has access to variables script1, script2 and script3
(//此函数可以访问变量script1、script2和script3)
oneScript();
twoScript();
threeScript();
});
});
});
The code above also loads three scripts in order. But, notice that it grows to the right. And here occurs the same problem as with callbacks. (上面的代码还按顺序加载三个脚本。但是,请注意,它向右生长。这里出现了与回调相同的问题。)
Frequently, when developers start just using promises, not knowing about chaining yet, they write it this way. (通常,当开发人员开始只是使用promise时,他们还不知道链接,他们就是这样编写的。)
Anyway, chaining is much more preferable. (无论如何,链式更可取。)
In cases when the nested function has access to the outer scope, it is acceptable to write .then directly. (如果嵌套函数可以访问外部作用域,则可以直接编写.then。)
In the case above, most nested callback has access to the overall variables: script1, script2, script3. Please, take into consideration that this is an exception rather than a rule.
Thenables
Thenables
A handler can return not only exactly a promise but a thenable” object, which represents an arbitrary object obtaining a method .then. It will be dealt with in the same way as a promise. (处理程序不仅可以准确地返回promise ,还可以返回thenable ”对象,该对象表示获取方法.then的任意对象。它将以与承诺相同的方式处理。)
The notion is that the third-party libraries possibly can perform their own “promise-compatible” objects. They may obtain a broad set of methods, at the same time being compatible with native promises as they perform .then. For a better perception of thenable objects, look at the example below:
class Thenable {
constructor(number) {
this.number = number;
}
then(resolve, reject) {
console.log(resolve); // function() { native code }
// resolve with this.number+2 after the 2 second
(//在2秒后使用this.number+2解析)
setTimeout(() => resolve(this.number + 2), 2000);
}
}
new Promise(resolve => resolve(2))
.then(result => {
return new Thenable(result);
})
.then(console.log); // shows 4 after 2000ms
Bigger Example: fetch
Bigger Example: fetch
Frontend programmers use promises for network requests. In this part, we will consider an example of that. (前端程序员使用Promise进行网络请求。在这一部分,我们将考虑一个例子。)
Let’s use the fetch method for loading the information about the user from the remote server. Its basic syntax looks like this:
let promise = fetch(url);
The syntax above makes a network request to the url, returning a promise. The promise resolves with a response object in case the remote server responds with headers before the entire response is downloaded. (上面的语法向url发出网络请求,返回一个promise。Promise使用响应对象进行解析,以防远程服务器在整个响应下载之前使用标头进行响应。)
For reading the full response, it is necessary to call the method response.text(). Accordingly, this method returns a promise, which resolves when the entire text is downloaded from the remote server with that text as a consequence. (要读取完整响应,需要调用方法response.text ()。因此,此方法返回一个promise ,当从远程服务器下载包含该文本的整个文本时解析该promise。)
In the example below is demonstrated a request to the site.json and loading of the text from the server:
fetch('/promiseChaining/site.json')
// .then below works when the remote server responds
(//当远程服务器响应时,下面的.then工作)
.then(function (response) {
// response.text() returned a new promise
(//response.text ()返回了一个新的承诺)
//that resolved with the full response text
(//通过完整的响应文本解决)
// when it loaded
(//当它加载时)
return response.text();
})
.then(function (text) {
// and here's the content of the remote file
(//这是远程文件的内容)
console.log(text); // {"name": "w3cdoc", isBook: true}
});
The response object returned from the fetch involves the method response.json(), which can read the remote data parsing it as JSON. (从fetch返回的响应对象涉及方法response.json () ,该方法可以读取将其解析为JSON的远程数据。)
Now, let’s try switching to it using arrow functions:
// same as above, but response.json() parses remote content as JSON
fetch('/promiseChaining/site.json')
.then(response => response.json())
(.then (response = > response.json ()))
.then(site => console.log(site.name)); // w3cdoc, got site name
Now, let’s see how the loaded user case works. (现在,让我们来看看已加载的用户案例的工作原理。)
For example, you can make another request to GitHub, load the user profile, as well as show the avatar, as follows:
// Make a request for user.json
fetch('/promiseChaining/user.json')
// Load it as json
(//作为json加载)
.then(response => response.json())
(.then (response = > response.json ()))
// Make a request to GitHub
(//向GitHub提出请求)
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// Load the response as json
(//将响应作为json加载)
.then(response => response.json())
(.then (response = > response.json ()))
// Show the avatar image for 2 seconds
(//显示头像2秒)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatarUrl;
img.className = "promiseAvatarExample";
document.body.append(img);
setTimeout(() => img.remove(), 2000); // (*)
});
The code above works, but an error exists in it. (上面的代码有效,但其中存在错误。)
As a rule, an asynchronous action always returns a promise. It allows planning actions after it. Even if you are not going to extend the chain currently, you might need it later. (通常,异步操作总是返回一个promise。它允许在它之后规划操作。即使您目前不打算延长链条,以后也可能需要它。)
Finally, here is the option of splitting the code into reusable functions:
function loadJson(url) {
return fetch(url)
(return fetch (url))
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(user) {
return new Promise(function (resolve, reject) {
let img = document.createElement('img');
img.src = user.avatarUrl;
img.className = "promiseAvatarExample";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(user);
}, 2000);
});
}
// Use them:
loadJson('/promiseChaining/user.json')
.then(user => loadGithubUser(user.name))
(.then (user = > loadGithubUser (user.name)))
.then(showAvatar)
(.then (showAvatar))
.then(user => console.log(`Finished showing ${user.name}`)); // ...
Summary
Summary (概要)
JavaScript promises can be chained together for using the settled promises (resolved or rejected), as well as the promises’ values to control the flow of execution. It is useful when there is a sequence of asynchronous operations to perform that each of them depends on the results of the previous operation. The next step of the chain will not be performed until the preceding one is resolved. The chains of promises also involve error propagation. The chain of promises may be handled once.
As a rule, chaining promises is performed with the following methods of the promise object prototype: .then and .catch.
The .then method determines the action to run when a promise is fulfilled. The .catch method determines what to do in case the promise is rejected. (.then方法确定在履行承诺时要运行的操作。.catch方法确定在promise被拒绝的情况下该怎么做。)