- 异步任务需要一个接一个的执行(如执行脚本),所以可以使用Promise链
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result);
return result * 2;
}).then(function(result) {
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
- 是按照
new promise() -> .then -> .then...
的顺序执行,只有在上一个完全执行完毕之后,才可以执行下一个 - 从技术上讲,我们也可以将多个 .then 添加到一个 promise 上。但这并不是 promise 链(chaining)。
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
返回 promise
- .then中的处理程序可以创建并返回一个promise
- 其它处理程序要等待它settled后再获得其结果
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
示例:loadScript
- 按顺序依次加载脚本
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// 使用在脚本中声明的函数
// 以证明脚本确实被加载完成了
one();
two();
three();
});
- 用箭头函数来重写
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// 脚本加载完成,我们可以在这儿使用脚本中声明的函数
one();
two();
three();
});
- 如果向每个loadScript中直接添加.then,会出现“厄运金字塔”现象
- 所以一般不用下面的写法,链式是首选
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// 此函数可以访问变量 script1,script2 和 script3
one();
two();
three();
});
});
});
Thenables
- 处理程序返回的不完全是一个
promise
,而是返回被称为"thenable"
对象(一个具有方法.then
的任意对象),它会被当做一个promise
来对待 - 第三方库可以实现自己的"
promise 兼容
"对象。它们可以具有扩展的方法集,但也与原生的promise
兼容,因为它们实现了.then
方法 - 这个特性允许我们将自定义的对象与
promise
链集成在一起,而不必继承自Promise
。
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// 1 秒后使用 this.num*2 进行 resolve
setTimeout(() => resolve(this.num * 2), 1000);
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result);
})
.then(alert); // 1000ms 后显示 2
fetch
- promise 通常被用于网络请求
- fetch方法从远程服务器加载用户信息
let promise = fetch(url);
- 向url发出网络请求返回一个promise。当远程服务器返回 header(是在 全部响应加载完成前)时,该 promise 使用一个 response 对象来进行 resolve。
向user.json发送请求,并从服务器加载该文本
fetch('/article/promise-chaining/user.json')
// 当远程服务器响应时,下面的 .then 开始执行
.then(function(response) {
// 当 user.json 加载完成时,response.text() 会返回一个新的 promise
// 该 promise 以加载的 user.json 为 result 进行 resolve
return response.text();
})
.then(function(text) {
// ...这是远程文件的内容
alert(text); // {"name": "iliakan", "isAdmin": true}
});
从fetch
返回的response
对象还包括response.json()
方法,该方法读取远程数据并将其解析为 JSON
。
// 同上,但是使用 response.json() 将远程内容解析为 JSON
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan, got user name
案例:向GitHub发送一个请求,加载用户个人资料并显示头像
// 发送一个对 user.json 的请求
fetch('/article/promise-chaining/user.json')
// 将其加载为 JSON
.then(response => response.json())
// 发送一个到 GitHub 的请求
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// 将响应加载为 JSON
.then(response => response.json())
// 显示头像图片(githubUser.avatar_url)3 秒(也可以加上动画效果)
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
添加在头像移除之后显示一个用于编辑该用户或者其它内容的表单
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
//new Promise()
.then(githubUser => new Promise(function(resolve, reject) { // (*)
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser); // 返回该用户的名字
}, 3000);
}))
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
- 因为上面的程序按照链式依次调用,只有在
setTimeout
中的resolve(githubUser)
被调用后才会变为settled
. - 更好的做法是将异步行为封装成一个函数,该函数始终返回一个
promise
- 如下:我们可以将代码拆分成可重用的函数
function loadJson(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(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// 使用它们:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
总结
如果 .then
(或 catch/finally
都可以)处理程序返回一个 promise
,那么链的其余部分将会等待,直到它状态变为 settled
。当它被 settled
后,其 result
(或 error
)将被进一步传递下去。