1. 什么是 Promise
1.1. 定义
- Promise 对象用于异步计算
- 一个 Promise 表示一个现在、将来或永不可能用到的值
1.2. 优缺点
1.2.1. 优点
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
- Promise 对象提供统一的接口,使得控制异步操作更加容易
1.2.2. 缺点
- 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
- 当处于 Pending 状态时,无法得知目前进展到哪一个阶段
1.3. 使用方法
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
- Promise 状态发生变化,就会触发
.then()
里的响应函数处理后续步骤 - Promise 状态最多只能改变一次,一经改变,不能再变
1
2
3
4
5
6
7
8
9
10
11
12
13
14new Promise(
// 执行器 executor
function (resolve, reject) {
// 一段耗时很长的异步操作
resolve(); // 数据处理完成
reject(); // 数据处理出错
}
).then(function A() {
// 处理成功 下一步操作
}, function B() {
// 处理失败 下一步操作
})
1.4. Promise 的 3 种状态
状态 | 含义 | 描述 |
---|---|---|
pending |
待定 | 初始状态 |
fulfilled |
实现 | 操作成功 |
rejected |
被否决 | 操作失败 |
1.5. 最简单的实例
1 | // 先输出 here we go,等待 2s 后输出 hello world |
1.6. 使用 Promise 实现 Ajax
1 | function ajax(URL) { |
1.7. 浏览器兼容性
2. 异步的问题
2.1. 异步操作的常见语法
事件的侦听与响应
1
2
3
4
5
6
7function start() {
// 响应事件,进行对应的操作
}
// JavaScript
document.getElementById('start').addEventListener('click', start, false);
// jQuery
$("#start").on('click', start);回调
1
2
3
4
5
6
7
8
9
10
11// ajax
$.ajax({
url: 'https://baidu.com',
success: function (res) {
// 回调函数
}
});
// 页面加载完毕之后回调
$(function () {
// 回调函数
});
2.2. 异步回调的问题
- 嵌套层次很深,难以维护
回调地狱1
2
3
4
5
6
7
8
9a(function (arg1) {
b(arg1,function (arg2) {
c(arg2,function (arg3) {
d(arg3,function () {
// 回调地狱
})
})
})
});
- 无法正常的使用 return 和 throw
不能捕获异步函数的异常,因为异步函数是在不同的栈里面运行,没办法正常的使用 try catch 处理异步函数中的错误。 - 无法正常检索堆栈信息
每次回调都是在系统层面的一个新的堆栈 - 多个回调之间难以建立联系
一个回调一旦启动,我们再也没有办法对它操作
3. 使用 Promise
3.1. 两步执行
1 | // 隔 2s 后输出 hello world,再隔 2s 后输出 hello promise |
3.2. .then()
.then()
接收两个函数作为参数,分别代表fulfilled
和rejected
.then()
返回一个新的 Promise 实例,所以它可以链式调用- 当前面的 Promise 状态改变时,
.then()
根据其最终状态,选择特定的状态响应函数执行 - 状态响应函数可以返回新的 Promise,或者其它值;如果返回其它值,则会立刻执行下一级
.then()
- 如果返回新的 Promise,那么下一级
.then()
会在新 Promise 状态改变之后执行
3.3. 错误处理
Promise 会自动捕获内部异常,并交给 rejected 响应函数处理。强烈建议在所有队列最后都加上 .catch()
,以避免漏掉错误处理造成意想不到的问题。
处理错误的两种方法
- rejected(‘错误信息’) .then(null, message => {});
- throw new Error(‘错误信息’) .catch(message => {});
推荐使用 catch 捕获异常,更加清晰好读,并且可以捕获前端的错误。
1 | console.log('here we go'); |
4. Promise 常用函数
4.1. Promise.all()
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。1
Promise.all([p1, p2, p3]);
上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
p 的状态由 p1、p2、p3 决定,分成两种情况:
- 只有 p1、p2、p3 的状态都变成 fulfilled,p的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数
- 只要 p1、p2、p3 之中有一个被 rejected,p的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数
1 | console.log('here we go'); |
4.2. Promise.race()
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。1
var p = Promise.race([p1, p2, p3]);
上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的返回值。
如果 Promise.all 方法和 Promise.race 方法的参数,不是 Promise 实例,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。
4.3. Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用。1
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代码将 jQuery 生成 deferred 对象,转为一个新的 ES6 的 Promise 对象。
如果 Promise.resolve()
方法的参数,不是具有 then
方法的对象(又称 thenable 对象
),则返回一个新的 Promise 对象,且它的状态为 fulfilled。1
2
3
4
5var p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s); // Hello
});
上面代码生成一个新的 Promise 对象的实例 p,它的状态为 fulfilled,所以回调函数会立即执行,Promise.resolve()
方法的参数就是回调函数的参数。
如果 Promise.resolve()
方法的参数是一个 Promise 对象的实例,则会被原封不动地返回。
4.4. Promise.reject()
Promise.reject()
方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。Promise.reject()
方法的参数会被传递给实例的回调函数。1
2
3
4
5var p = Promise.reject('出错了');
p.then(null, function (s) {
console.log(s); // 出错了
});
上面代码生成一个 Promise 对象的实例 p,状态为 rejected,回调函数会立即执行。
5. 兼容性说明
如果需要在 IE 中使用 Promise,方法有两种:
- 只实现异步队列
jQuery.defered - 兼容所有平台
Bluebird
Promise polyfill