Promise 的含义
Promise 是异步编程的一种解决方案,比起传统的解决方案如:事件的监听和回调函数,要更加强大。ES6 将其写进了语言标准中,统一了用法,原生提供了Promise对象。所谓 Promise,简单来说就是一个装载着在未来才会开始和结束的事件的容器,Promise 有以下两个特点:
-
Promise 的状态不受外界影响。Promise 中装载着一个在未来才会开始和结束的事件,比如从服务器获取数据,那么 Promise 理所当然地代表着一个异步操作,其拥有着三种状态
- pending,进行
- fulfilled,已成功
- rejected,已失败
只有这个异步操作的结果才能决定 Promise 当前处于哪一种状态中,其他任何手段都无法改变这个状态,从这句话我们可以了解到 Promise 之所以叫做Promise(承诺)的原因,Promise 承诺在未来会执行其装载的异步操作,且只有这个异步操作的结果能够改变我的状态
-
Promise 的状态一旦改变,就不会再次改变,任何时候都可以得到这个结果。Promise状态的改变只能是以下两种状况:
- pending(进行)——> fulfilled(已成功)
- pending(进行)——> rejected(已失败)
只要上述两种状况中的一种发生,Promise 的状态就会凝固,一直保持不变,这个时候就称Promise 对象 resolved(已定型),(其实我们也可以将 resolved 称其为 Promise 的一个状态,其包含了 fulfilled 和 rejected 两种状态,可是状态需要是精确的,不能像薛定谔的猫一样无法被确定,所以我更愿意称 resolved 为一种概括,为了后续行文简便,我仍称 resolved 为 Promise 的一种状态),如果你再向已经 resolved 的 Promise 添加回调函数,也会立即得到这个结果,这个现象和事件的监听完全不同,事件的监听特点是你一旦错过事件发生的时间点,是得不到结果的
Promise 的基本用法
let promise = new Promise(function(resolve, reject) {
// some code...
if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
})
Promise 的构造器接受一个函数作为参数,该函数有两个参数是 resolve 和 reject,它们也是两个函数,由 JavaScript 引擎提供,不需要自己实现,针对这两种状态:
- resolve:异步操作的结果作为参数,在异步操作成功的时候被调用,并将 Promise 的状态从 pending 变为 fulfilled
- reject:异步操作抛出的错误作为参数,在异步操作失败的时候被调用,并将 Promise 的状态从 pending 变为 rejected
在 Promise 实例生成后可以用 then 方法分别注册 fulfilled 状态和 rejected 状态的回调函数
promise.then(function(value) {
// success,some code...
}, function(error) {
// failure,some code...
});
-
then 函数接收两个函数作为参数,第一个函数是 fulfilled 状态的回调函数,第二个函数是 rejected 状态的回调函数,其中第一个函数是必须的,第二个函数是可选的
-
catch 函数是函数 then(null, rejection) 或者 then(undefined, rejection) 的别名,用于指定 rejected 状态的回调函数
通常我们不会在 then 函数中同时注册 fulfilled 状态和 rejected 状态的回调函数,因为单纯地只把 fulfilled 状态的回调函数注册在 then 函数中,把 rejected 状态的回调函数注册在 catch 函数中,这样更具有语义化,也更容易理解
let promise = new Promise(function(resolve, reject) {
console.log('Promise start');
resolve();
});
promise.then(() => console.log('Promise fulfilled'));
console.log('Hi');
// output:
// Promise start
// Hi
// Promise fulfilled
Promise 新建后就会立即执行,所以第一行输出 ‘Promise start’,Promise是异步的,所以 then 函数中的操作会在本轮宏任务完成之后再执行,所以第二行输出‘Hi’,第三行输出‘Promise fulfilled’(有关‘宏任务’可以查找JavaScript的执行机制有关内容)
如果调用 resolve 和 reject 函数时带有参数的话,那么它们的参数会被传递给 then 函数中注册的回调函数,如果 resolve 函数中的参数是另一个 Promise 对象的话,会出现什么情况呢?
let promise1 = new Promise(function (resolve, reject) {
// some code...
});
let promise2 = new Promise(function (resolve, reject) {
// some code...
resolve(promise1);
});
这时,promise1 的状态就会传递给 promise2,也就是 promise1 的状态取代了 promise2 的状态,如果 promise1 的状态此时是 pending 那么 promise2 就会等待 promise1 的状态改变之后再执行回调函数,如果 promise1 的状态已经是 resolved 或者 rejected,那么 promise2 的回调就会立刻执行
let promise1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000);
});
let promise2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(promise1), 1000);
});
promise2.then((value) => console.log('value: ' + value))
.catch((error) => console.log('error: ' + error)); // 链式写法,见下文
上述代码的执行过程如下:
- 此时 promise1 的状态为 pending,在 3s 后改变为 rejected
- 此时 promise2 的状态为 pending,在 1s 后改变为 resolveed
- 1s 后,由于 promise2 中的 resolve 函数的参数为 promise1,所以 promise2 的状态被 promise1 的状态所取代,所以此时 promise2 的状态仍为 pending
- 3s 后,promise2 为 rejected,执行函数 catch 中注册的回调函数
- 打印 ‘Error:fail’
Promise 的链式执行
then 函数返回的是一个新的 Promise 实例,因此可以采用链式写法
promise.then((json) => return json.name)
.then((name) => console.log(name));
上述代码使用 then 函数,依次注册了两个回调函数,当第一个回调函数执行完毕后,会将其返回的结果作为参数传入第二个回调函数
如果第一个回调函数返回的结果是一个 Promise 实例的话,如同上文所述,第二个回调函数就会等待该 Promise 实例的状态发生变化,才会被执行
promise.then(function (url) {
return getJSON(url); // 返回一个 Promise 实例
}).then(function (json) {
console.log('promise resolved: ' + json.content);
}).catch(function (error) {
console.log('promise rejected: ' + error);
});
另外,Promise 实例的错误具有 ‘冒泡’ 性质,会一直往执行链的后端传递,直至被捕获为止,在上述代码中一共有 4 个 Promise 实例,一个 promise、一个由 getJSON 方法创建、两个由 then 方法创建,它们其中仍和一个抛出的错误都会被最后的 catch 捕获
Promise.prototype.finally() 方法用于注册无论 Promise 实例最后的状态如何都会执行的操作,其不接收任何参数,也就等同于无法知道该 Promise 实例的状态如何,不依赖于 Promise 实例的执行结果
promise.then(() => /* some code... */)
.catch(() => /* some code... */)
.finally(() => /* some code... */);
Promise.all() 方法用于把把多个 Promise 实例包装成一个新的 Promise 实例,注意不是 Promise 实例的数组
const biggestPromise = Promise.all([promise1, promise2, promise3]);
biggestPromise 的状体由 promise1、promise2、promise3 共同决定,有以下两种状况:
- 当 promise1、promise2、promise3 的状态都为 fulfilled 时,biggestPromise 的状态为 fulfilled,此时,前三者的返回值组成一个数组,作为后者回调函数的参数
- 当 promise1、promise2、promise3 的状态有一个为 rejected ,biggedPromise 的状态为 rejected,此时,第一个被 reject 的 Promise 实例的返回值,作为后者回调函数的参数
Promise.race() 方法同样是把多个 Promise 实例包装成一个新的 Promise 实例
const fastestPromise = Promise.race([promise1, promise2, promise3]);
只要 promise1、promise2、promise3 之中有一个实例率先改变状态,fastestPromise 的状态就会改变,率先改变状态的 Promise 实例的返回值,作为 fastestPromise 回调函数的参数
水平所限,难免谬误,请指正…