Promise的含义
Promise是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理更强大。
从语法上理解是一个构造函数可以进行对象的实例化,该对象可以获取异步操作的消息。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。
- 对象的状态不受外界影响。 Promise 对象代表一个异步操作,有三种状态: pending 、 fulfilled 和 rejected 。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从 pending -> fulfilled 和 pending -> rejected 。只要这两种情况发生,状态就凝固了,不会再改变。
基本使用
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创建一个Promise实例。
const promise = new Promise(function (resolve, reject) {// ... some codeif(/* */){resolve(value)}else{reject(error)}})
Promise构造函数接受一个函数作为对象,该函数有两个参数分别是 resolve 和 reject 。它们是两个函数,由JavaScript引擎提供,不用自己部署。
resolve() 作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject() 作用是:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise 实例生成后,可以用 then() 方法分别指定 resolved 状态和 rejected 状态的回调函数。
promise.then(function(value){// success},function(error){// failure})
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态为 resolved 时调用,第二个回调函数是Promise对象状态为 rejected 时调用。这两函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。
Promise.prototyoe.then()
Promise 实例具有 then 方法。作用是为Promise实例添加状态改变时的回调函数,具体如下所述。
then() 方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then() 方法返回一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
返回值:
Promise.prototype.catch()
方法是 .then(null,rejection) 或者 .then(undefined,rejection) 的别名,用于指定发生错误时的回调函数。catch为then的语法糖。
- 下面是一个Promise对象的简单例子(来自ECMAScript6入门)
```javascript
function timeout(ms) {
return new Promise((resolve, reject) => {
}); }setTimeout(resolve, ms, 'done'); // 计时器三个参数是作为第一个函数参数的实参
timeout(2000).then((value) => { console.log(value); });
解析:timeout函数返回一个Promise实例,表示一段时间后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为了resolved,就会触发then方法绑定的回调函数。<br />无论then还是catch都会返回一个新的Promise对象,- **Promise 新建后就会立即执行。**<a name="xy9mT"></a>#### Promise封装 - AJAX请求- 封装一个函数 sendAJAX发送GET AJAX请求 参数URL 返回结果是Promise对象```javascriptfunction sendAJAX(url) {return P = new Promise((resolve, reject) => {// 1.创建ajax对象const xhr = new XMLHttpRequest()// 设置响应体数据格式xhr.responseType = 'json'// 2.建立连接xhr.open('GET', url)// 3.发送请求xhr.send()// 4.处理响应结果xhr.onreadystatechange = function () {// 判断ajax的状态码,4为浏览器解析响应体完毕,可以正常使用,对象读取响应结束if (xhr.readyState === 4) {// 判断响应状态码 200-299if (xhr.status >= 200 && xhr.status < 300) {// 控制台输出响应体resolve(xhr.response)} else {// 控制台输出响应状态码reject(xhr.status)}}}})}sendAJAX('https://api.apiopen.top/getJoke').then(value => { console.log(value) }, error => { console.warn(error) })
Promise 基本工作流程:

Promise函数对象的API
以下方法属于Promise这个函数对象,非实例对象。
Promise.resolve()
作用:将现有对象转为Promise对象。
const P1 = Promise.resolve('foo')// 等价于const P2 = new Promise(resolve => resolve('foo'))console.log(P1 == P2) // false
Promise.resolve()方法的参数分成四种情况:
- 参数是一个Promise实例
- 参数是一个thenable对象
- 参数不是具有then()方法的对象,或根本不是对象
- 不带有任何参数
总结:
传入参数为 非Promise类型的对象,则返回的结果为成功promise对象
传入参数为 Promise对象,则参数的结果决定了resolve的结果
Promise.reject()
Promise.reject(reason) 方法返回一个新的 Promise 实例,该实例的状态为 rejected 。
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const P = Promise.all([p1, p2, p3]);
Promise.all() 方法接受一个数组作为参数,数组每一项都是Promise实例,如果不是,就会先调用上面讲到的Promise.resolve()方法,将参数转为Promise实例,再进一步处理。
P的状态由p1、p2、p3决定,分成两种情况:
- p1、p2、p3的状态都为 fulfilled ,P的状态才会变成 fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给P的回调函数
- p1、p2、p3中有一个被 rejected ,P的状态就会变成rejected,此时第一个被reject的实例的返回值,会传递给P的回调函数。 ```javascript const p1 = new Promise((resolve, reject) => { // setTimeout(resolve, 2000, ‘p1成功’) resolve(‘p1成功’) }) const p2=new Promise((resolve,reject)=>{ // setTimeout(resolve, 2000, ‘p2成功’) setTimeout(reject, 2000, ‘p2失败’) }) const p3 = Promise.resolve(‘P3成功’)
const P = Promise.all([p1, p2, p3]) .then(result => { console.log(‘result’,result) console.log(‘成功’,P) },result=>{ console.log(‘result’,result) console.log(‘失败’,P) }) console.log(P)
上面代码中,promise是包含3个Promise实例的数组,只有这个3个实例的状态都变成 fulfilled ,或者其中一个变为 rejected ,才会调用 Promise.all 方法后面的回调函数。<br /><br />⚠️**注意**,如果作为参数的Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。(then也是同理)```javascriptconst p2 = new Promise((resolve, reject) => {// setTimeout(resolve, 2000, 'p2成功')setTimeout(reject, 2000, 'p2失败')})// .then(resolve => { console.log(resolve) }).catch(error => { console.log('catch', error) })
上述代码中,p2会rejected,但是p2有自己的catch方法,该方法返回的是一个新的Promise实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里的3个实例都会resolved。因此有了如下输出。
Promise.race()
Promise.race() 方法同样是将多个Promise实例,包装成一个新的Promise实例。
const P = Promise.race([p1, p2, p3]);
上述代码中,只要p1、p2、p3之中有一个实例率先改变状态,P的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给P的回调函数。
返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。
- 案例:如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve
```javascript
const p = Promise.race([
fetch(‘https://api.apiopen.top/getJoke‘),
new Promise(function (resolve, reject) {
}) ]);setTimeout(() => reject(new Error('request timeout')), 5000)
p .then(console.log) .catch(console.error);
上述代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法的指定的回调函数。<a name="DRiAF"></a>## 硬核 - Promise封装<a name="ZNSMN"></a>### promise几个关键问题<a name="L8XkI"></a>#### 1. 如何改变Promise的状态?resolve()、reject()、抛出异常。```javascriptconst P = new Promise((resolve,reject)=>{// 1. resolve('ok'); // pending => fulfilled// 2. reject('error'); // pending => rejected// 3. throw '出现了问题'; // pending => rejected})console.log(P)
2. 一个Promise指定多个成功/失败回调函数,都会调用吗?
当Promise改变为对应状态时都会调用
const p = new Promise((resolve, reject) => {resolve('成功啦');resolve('成功');})// 指定回调 - 1p.then(value => {console.log('第一个then',value);})// 指定回调 - 2p.then(value => {console.log('第二个then',value);})
3. 改变Promise状态和指定回调函数谁先谁后?
当改变状态的函数是异步任务时,then方法的回调函数先执行。
- 如何先改变状态再指定回调?
(1)在执行器中直接调用resolve()/reject()
(2)延迟更长时间再调用then()
- 什么时候才能得到数据?
(1)如果先执行指定回调,那当状态发生改变时,回调函数就会调用,得到数据
(2)如果先执行改变状态,那当指定回调时,回调函数就会调用,得到数据
4. Promise.then()返回的新Promise的结果状态由什么决定?
由then()指定的回调函数执行的结果决定 <br />(1)如果抛出异常,新的Promise变为rejected,reason为抛出的异常<br />(2)如果返回的是非Promise的任意值,新Promise变为resolved,value为返回的值 <br />没有返回值,即是返回undefined,新Promise变为resolved,value为undefined<br />(3)如果返回的是另一个新Promise,此Promise的结果就会成为新Promise的结果
const P=new Promise((resolve,reject)=>{resolve('ok')})const result =P.then(value=>{// throw '出了问题' // rejected / 出了问题// return '321' // fulfilled / '321'return new Promise((resolve,reject)=>{resolve('OKK') // fulfilled / OKK// reject('ONN') // rejected / ONN})},error=>{console.log(error)})console.log('result',result)
5. Promise如何串联多个操作任务?
因为Promise的then()返回一个新的Promise,通过then的链式调用串联多个同步/异步任务
6. Promise的异常穿透
当使用Promise的then链式调用时,可以在最后指定失败的回调,前面任何操作出了异常,都会传到最后失败的回调中处理。
const P = new Promise((resolve, reject) => {setTimeout(() => {reject('失败');}, 1000)})P.then(value => {console.log(value, 111);}).then(value => {console.log(value, 222);}).then(value => {console.log(value, 333);}).catch(error => {console.warn(error); // 失败})
7. 中断Promise链
当使用Promise的then链式调用时,在中间中断,不再调用后面的回调函数。
方法:有且只要一种,在回调函数中返回一个pendding状态的Promise对象
const P = new Promise((resolve, reject) => {setTimeout(() => {resolve('成功');}, 1000)})P.then(value => {console.log(value, 111);// 有且只有一个方法 ⬇️return new Promise(() => { })}).then(value => {console.log(value, 222);}).then(value => {console.log(value, 333);}).catch(error => {console.warn(error);})
∆手写promise
简易版
class Promise {constructor(executor) {this.state = 'pending';this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];try { // 如果executor执行报错,直接执行rejectexecutor(this.resolve, this.reject);} catch (err) {reject(err);}}//成功resolv(value) {// state改变,resolve调用就会失败if (this.state === 'pending') {// resolve调用后,state转化为成功态this.state = 'fulfilled';// 储存成功的值this.value = value;}};//失败reject(reason) {// state改变,reject调用就会失败if (this.state === 'pending') {// reject调用后,state转化为失败态this.state = 'rejected';// 储存失败的原因this.reason = reason;}};//then方法then(onFulfilled, onRejected) {// 声明返回的promise2let promise2 = new Promise((resolve, reject) => {if (this.state === 'fulfilled') {let x = onFulfilled(this.value);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2, x, resolve, reject);};if (this.state === 'rejected') {let x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);};if (this.state === 'pending') {this.onResolvedCallbacks.push(() => {let x = onFulfilled(this.value);resolvePromise(promise2, x, resolve, reject);})this.onRejectedCallbacks.push(() => {let x = onRejected(this.reason);resolvePromise(promise2, x, resolve, reject);})}});// 返回promise,完成链式return promise2;}}
复杂版
function MyPromise(fn) {this.PromiseState = 'pending';this.PromiseResult = undefined;// 注册该 MyPromise对象resolve任务函数this.thenCallback = undefined;// 注册该 My Promise对象reject任务函数this.catchCallback = undefined;var _this = this;var resolve = function (resolveValue) {// console.log(this) // 该this指向Windowif (_this.PromiseState === 'pending') {// 状态和值变更_this.PromiseState = 'fulfilled';_this.PromiseResult = resolveValue;// 利用setTimeout模拟Promise对象的异步控制// 虽然resolve是在then函数前执行的// 但是该函数的回调一定是在Promise对象初始化完毕后执行的// 所以我们的回调执行时,thenCallback就已经初始化完毕了if (resolveValue instanceof MyPromise) {// 当传入的 resolveValue 的值的类型是 MyPromise对象本身时// 不需要使用异步控制,直接用它的then去处理下一步的流程resolveValue.then(function (res) {if (_this.thenCallback) {_this.thenCallback(res);}});} else {setTimeout(function () {if (_this.thenCallback) {_this.thenCallback(resolveValue);}});}}}var reject = function (rejectValue) {if (_this.PromiseState === 'pending') {_this.PromiseState = 'rejected';_this.PromiseResult = rejectValue;setTimeout(function () {// 当只有 catchCallback 的时候代表直接写的catch直接发流程即可if (_this.catchCallback) {_this.catchCallback(rejectValue);} else if (_this.thenCallback) {// 如果没有 catchCallback 但是存在 thenCallback 代表Promise对象直接使用了then// 所以此时应该先让then执行来进行本次函数的跳过,直接找到catch_this.thenCallback(rejectValue)} else {throw (`(in promise) / no catch found ${_this.PromiseResult}`)}})}}if (fn) {fn(resolve, reject);} else {throw ('Uncaught TypeError: Promise resolver undefined is not a functionat new Promise (<anonymous>)');}}MyPromise.prototype.then = function (callback) {var _this = this;// 实现链式调用,需要return回去一个新的Promise对象return new MyPromise(function (resolve, reject) {// 我们通常在then函数执行的时候优先在函数内部注册回调函数任务// 等待resolve执行的时候通过注册异步的任务来在该回调后捕获它_this.thenCallback = function (value) {// 由于catch的链式调用比较复杂// 所以可能在catch执行的时候会触发 thenCallback// 所以在此需要判断当前Promise对象的状态是不是 rejected(已拒绝)if (_this.PromiseState === 'rejected') {// 如果是 rejected 触发的thenCallback,直接调用下一个对象的rejectreject(value);} else {var res = callback(value); // 获取then方法的返回值// 判断。如果某一次then函数返回的是一个 rejected 状态的Promise对象// 此时我们需要在这里直接注册它的catch,并且在catch内部拿到对象的结果// 然后通过下一个对象的reject链式的通知最近的catch函数执行if (res instanceof MyPromise && res.PromiseState === 'rejected') {res.catch(function (errvalue) {reject(errvalue);})} else {resolve(res);}}}})}MyPromise.prototype.catch = function (callback) {var _this = this;return new MyPromise(function (resolve, reject) {_this.catchCallback = function (value) {var res = callback(value)// 由于catch本次的对象为rejected状态,但是如果继续调用默认触发的还是then函数resolve(res);}})}MyPromise.resolve = function (value) {return new MyPromise(function (resolve, reject) {resolve(value);})}MyPromise.reject = function (value) {return new MyPromise(function (resolve, reject) {reject(value);})}MyPromise.all = function (PromiseArr) {var resArr = [];return new MyPromise(function (resolve, reject) {// PromiseAll的特点是,必须等待PromiseArr数组中所有的Promise状态都为 fulfilled 之后才会触发thenPromiseArr.forEach((promiseItem, index) => {promiseItem.then(function (res) {resArr[index] = res;let success = PromiseArr.every(item => {return item.PromiseState === 'fulfilled';})if (success) {resolve(resArr);}}).catch(function (err) {reject(err);})})})}MyPromise.race = function (PromiseArr) {return new MyPromise(function (resolve, reject) {PromiseArr.forEach((promiseItem, index) => {promiseItem.then(function (res) {resolve(res);}).catch(function (err) {reject(err);})})})}
实现 Primise.all
1)核心思路
- 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
- 这个方法返回一个新的 promise 对象,
- 遍历传入的参数,用Promise.resolve()将参数”包一层”,使其变成一个promise对象
- 参数所有回调成功才是成功,返回值数组与参数顺序一致
- 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
2)实现代码
一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了~
function promiseAll(promises) {return new Promise(function (resolve, reject) {if (!Array.isArray(promises)) {throw new TypeError(`argument must be a array`)}var resolvedCounter = 0;var promiseNum = promises.length;var resolvedResult = [];for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value => {resolvedCounter++;resolvedResult[i] = value;if (resolvedCounter == promiseNum) {return resolve(resolvedResult)}}, error => {return reject(error)})}})}// testlet p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)}, 1000)})let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)}, 2000)})let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)}, 3000)})promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]})
