Promise的含义
Promise是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理更强大。
从语法上理解是一个构造函数可以进行对象的实例化,该对象可以获取异步操作的消息。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。
- 对象的状态不受外界影响。 Promise 对象代表一个异步操作,有三种状态: pending 、 fulfilled 和 rejected 。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从 pending -> fulfilled 和 pending -> rejected 。只要这两种情况发生,状态就凝固了,不会再改变。
基本使用
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创建一个Promise实例。
const promise = new Promise(function (resolve, reject) {
// ... some code
if(/* */){
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 新建后就会立即执行。**
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21370038/1621859291256-baca4812-5f32-4a90-a0af-ada55437fa5a.png#height=164&id=az0Ow&margin=%5Bobject%20Object%5D&name=image.png&originHeight=328&originWidth=690&originalType=binary&ratio=1&size=82049&status=done&style=none&width=345)
<a name="xy9mT"></a>
#### Promise封装 - AJAX请求
- 封装一个函数 sendAJAX发送GET AJAX请求 参数URL 返回结果是Promise对象
```javascript
function 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-299
if (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 />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21370038/1621926823557-5d3ff760-176a-4889-b3dd-41ff7d184e5a.png#height=130&id=uYpWo&margin=%5Bobject%20Object%5D&name=image.png&originHeight=260&originWidth=982&originalType=binary&ratio=1&size=177879&status=done&style=none&width=491)<br />⚠️**注意**,如果作为参数的Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。(then也是同理)
```javascript
const 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()、抛出异常。
```javascript
const 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('成功');
})
// 指定回调 - 1
p.then(value => {
console.log('第一个then',value);
})
// 指定回调 - 2
p.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执行报错,直接执行reject
executor(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) {
// 声明返回的promise2
let 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指向Window
if (_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,直接调用下一个对象的reject
reject(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 之后才会触发then
PromiseArr.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)
})
}
})
}
// test
let 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]
})