Promise 对象的理解
Promise 含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise 对象有以下两个特点:
- 对象的状态不受外界影响。有三种状态,分别为 pending(进行中)、fulfilled(已成功)和 rejected(已失败)。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。状态改变只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。
使用 Promise 对象的好处在于:
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
- Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 缺点:
- 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
基本用法
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。
const promise = new Promise((resolve, reject) => { setTimeout(() => { const num = Math.random(); if (num > 0.5) { resolve(num); } else { reject(num); } }, 500); }); promise.then( res => { console.log("成功:" + res); }, err => { console.log("失败:" + err); } );
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
- resolve 函数的作用:将 Promise 对象的状态从“未完成(pending)”变为“成功(resolved)”,在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
- reject 函数的作用:将 Promise 对象的状态从“未完成(pending)”变为“失败(rejected)”在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- then 方法作用:接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。第二个函数可选,不一定要提供,也可以将第二个函数作为 catch 方法的参数。
- catch 方法作用:用于指定发生错误时的回调函数。Promise 对象异步操作抛出错误,状态就会变为 rejected,就会调用 catch 方法指定的回调函数处理这个错误。另外,then 方法指定的回调函数,如果运行中抛出错误,也会被 catch 方法捕获。
const promise = new Promise((resolve, reject) => { setTimeout(() => { const num = Math.random(); if (num > 0.5) { resolve(num); } else { reject(num); } }, 500); }); promise .then(res => { console.log("成功:" + res); }) .catch(err => { console.log("失败:" + err); }); promise .then(res => { console.log("成功:" + res); throw new Error("test"); }) .catch(err => { // num > 0.5时打印 "失败:Error: test" console.log("失败:" + err); });
Promise 执行顺序
Promise 新建后立即执行,then 方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,catch 同理。
调用 resolve 或 reject 并不会终结 Promise 的参数函数的执行。
const promise = new Promise((resolve, reject) => { console.log("我是第一个执行的"); resolve(); }); promise.then(res => { console.log("我是第三个执行的"); }); console.log("我是第二个执行的");
resolve 函数和 reject 函数的参数
reject 函数的参数通常是 Error 对象的实例,表示抛出的错误;resolve 函数的参数除了正常的值以外,还可能是另一个 Promise 实例。
如果一个 Promise(P2) 的 resolve 参数是另一个 Promise(P1),此时 P1 的状态就会传给 P2,P1 的状态决定了 P2 的状态,P1 的状态改变,P2 的回调函数才会执行。
const p1 = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("fail")), 3000); }); const p2 = new Promise(function(resolve, reject) { setTimeout(() => resolve(p1), 1000); }); p2.then(result => console.log(result)).catch(error => console.log(error)); // Error: fail
上面代码中,p1 是一个 Promise,3 秒之后变为 rejected。p2 的状态在 1 秒之后改变,resolve 方法返回的是 p1。由于 p2 返回的是另一个 Promise,导致 p2 自己的状态无效了,由 p1 的状态决定 p2 的状态。所以,后面的 then 语句都变成针对后者(p1)。又过了 2 秒,p1 变为 rejected,导致触发 catch 方法指定的回调函数。
Promise 链式调用
then 方法可以返回一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。
const promise = new Promise((resolve, reject) => { resolve("promise"); }) .then(res => { console.log(res); // promise return "promise1"; }) .then(res => { console.log(res); // promise1 return "promise2"; }) .then(res => { console.log(res); // promise2 });
注意:只要一个 Promise 中抛出错误,将执行 catch 方法,then 链终止。
const promise = new Promise((resolve, reject) => { resolve("promise"); }) .then(res => { console.log(res); // promise throw new Error("中止"); return "promise1"; }) .then(res => { console.log(res); return "promise2"; }) .then(res => { console.log(res); }) .catch(err => { console.log(err); // Error: 中止 });
主动终止 then 链,通过 catch 方法来中止 promise chain
const promise = new Promise((resolve, reject) => { resolve("promise"); }) .then(res => { console.log(res); // promise return Promise.reject({ notRealPromiseException: true }); }) .then(res => { console.log(res); return "promise2"; }) .then(res => { console.log(res); }) .catch(err => { if (err.notRealPromiseException) { return true; } console.log(err); });
Promise.prototype.finally()
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
finally 本质上是 then 方法的特例,不接受任何参数,不依赖于 Promise 的执行结果
promise.finally(() => { // 语句 }); // 等同于 promise.then( result => { // 语句 return result; }, error => { // 语句 throw error; } );
Promise.all()
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const promise = Promise.all([promise1, promise2, promise3])Promise.all 方法接受一个数组作为参数,promise、pro 米色、promise3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。(Promise.all 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。了解 Iterator 接口)
promise 的状态由 promise1、promise2、promise3 决定,分成两种情况。
- 只有 promise1、promise2、promise3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 promise1、promise2、promise3 的返回值组成一个数组,传递给 p 的回调函数。
- 只要 promise1、promise2、promise3 之中有一个被 rejected,promise 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 promise 的回调函数。
const p1 = new Promise((resolve, reject) => { resolve("hello"); }) .then(result => result) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error("报错了"); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // ["hello", Error: 报错了]
上面代码中,p1 会 resolved,p2 首先会 rejected,但是 p2 有自己的 catch 方法,该方法返回的是一个新的 Promise 实例,p2 指向的实际上是这个实例。该实例执行完 catch 方法后,也会变成 resolved,导致 Promise.all()方法参数里面的两个实例都会 resolved,因此会调用 then 方法指定的回调函数,而不会调用 catch 方法指定的回调函数。
如果 p2 没有自己的 catch 方法,就会调用 Promise.all()的 catch 方法。
Promise.race()
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3])只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
Promise.race 方法的参数与 Promise.all 方法一样,如果不是 Promise 实例,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。
Promise.resolve()
将现有对象转化为 Promise 对象。
const promise = Promise.resolve('Hello world')- 参数是 Promise 实例,该方法不做任何改变。
- 参数是一个 thenable 对象,先将对象转为 Promise 对象,然后立即执行 thenable 方法。相当于将 thenable 对象中的 then 方法处理的值作为参数传给 promise then 方法。
let thenObj = { then(resolve, reject) { resolve("Hello"); } }; const promise = Promise.resolve(thenObj); promise.then(res => { console.log(res); // Hello });
- 参数不是具有 then 方法的对象,或根本就不是对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。