手写Promise原理及实现
文章目录
- 前言
- 一、目的
- 二、Promise的概述
- 三、Promise基本结构
- 四、Promise异步执行
- 五、promise的then 方法特性
- 六、Promise的错误捕获
- 七、then方法的参数变成可选参数
- 八、promise.all方法实现
- 九、Promise.resolve 方法的实现
- 十、Promise.finally 方法的实现
- 十一、Promise.catch 方法的实现
- 十二、小结
前言
在我们工作中经常遇到Promise,而且Promise面试的时候是必问题,可能会涉及一些运算,可能让封装个promise,可能更深入的问原理,那我们今天就从零开始封装一个promise
一、目的
复杂的异步流程,就会出现大量的回调函数嵌套,会导致回调地狱问题,为了避免这种问题,CommonJS社区提出Promise规范,为了给异步编程统一提供一种更合理更强大的解决方案,在ES2015被标准化,成为语言规范;
二、Promise的概述
1、promise是一个对象,用来表示异步任务最终结束之后成功还是失败,就像是内部对外部的一个承诺,一开始这个承诺是待定状态Pending,有可能成功Fulfilled也有可能失败Rejected,状态明确之后都会有相应的任务去执行,分别为onFulfilled和onRejected,而且Promise明确的特点,状态一旦确定就不会再发生改变
2、promise其实是一个全局类型,在new实例的时候,参数是一个函数,这个函数就可以看成兑现承诺的逻辑,这个函数会在构造primise的过程中被同步执行,两个函数参数resolve和reject,resolve()作用将primise对象状态修改为fulfilled,一般将异步任务的回调任务通过resolve(参数)参数传递出去,任务达成,反之失败,失败的时候我们会传递一个错误对象作为参数,来表述错误理由,如new Error(‘primise reject’);通过实例对象调用then(onFulfilled事件回调(参数),onRejected回调(参数));
三、Promise基本结构
我们先回顾一下,我们平时都是怎么使用Promise的:
const promise = new Promise(function (resolve, reject) {
// 这里用于“兑现”承诺
// resolve(100) // 承诺达成
reject(new Error('promise rejected')) // 承诺失败
})
promise.then(function (value) {
// 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
console.log('resolved', value)
}, function (error) {
console.log('rejected', error)
})
我们根据这个使用可以看出:
1)、Promise 就是一个类。在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行。
2)、Promise 中有三种状态 分别为: 成功 fulfilled 、失败 rejected 、等待 pending
pending -> fulfilled
pending -> rejected
一旦状态确定就不可更改
3)、resolve和reject函数是用来更改状态的
resolve: fulfilled
reject: rejected
4)、then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的(因为每个promise都可以调用到then方法)
思路:开始用class来定义一个类;constructor来接收执行器;执行器是立即执行的,并且有两个参数,resolve和reject,定义resolve、reject属性用箭头函数定义,// 为什么它们是箭头函数呢?因为我们在promise中调用的时候是直接调用的,那么在普通函数中this的指向是window或者undefined,定义成箭头函数目的就是为了这个函数内部的this指向这个类就是这个实例对象也就是promise对象;resolve和reject是用来更改状态的,所以要有状态这个属性,状态属性是promise独有的所以定义成实例属性,如: status = PENDING;//这个状态定义成常量而不是直接赋值字符串,是因为常量每次调用的时候代码有提示,而且能复用;默认值是等待;调用resolve函数的时候是改变状态的值 // 将状态更改为成功 this.status = FULFILLED;reject // 将状态更改为失败this.status = REJECTED;又因为状态一旦更改不可改变,所以如果状态不是等待 阻止程序向下执行 if (this.status !== PENDING) return;每个promise对象都有个then方法,它应该是定义在原型对象中,有两个参数,一个成功回调一个失败回调,在then中我们做的事是判断promise状态,如果this.status === FULFILLED,调用成功的回调,反之亦然;成功和失败回调都要一个参数,分别表示成功之后的值和失败的原因(默认值是undefined),这两个值我们会在resolve和reject中拿到,将他们保存下来,在then的两个参数中会用到
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise{
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING;
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
}
then (successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value);
}else if (this.status === REJECTED) {
failCallback(this.reason);
}
}
}
测试封装的promise
index.ts
const MyPromise = require('./myPromise');
let promise = new MyPromise((resolve, reject) =>{
resolve('成功')
})
promise.then((value)=>{
console.log(value)
},(reason)=>{
console.log(reason)
}) // 输出:成功
四、Promise异步执行
上面只是同步执行的情况,下面我们加上考虑异步执行情况
异步情况时如果用我们上述代码会出现一个情况:
执行顺序:执行器立即执行,里面有个定时器异步代码,主线程是不会等待异步代码执行的,所以将它们放在等待队列里,接着执行then方法,因为没有调用resolve或者reject所以状态肯定是等待的,我们封装的then方法中没有考虑状态是等待的情况,所以我们要判断一下状态,什么时候调它,当状态变为成功或失败的时候调相应的回调,所以先将成功和失败回调存储起来,2秒之后调用resolve或者reject时,先判断一下是否有成功或者失败回调,有就执行。
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise{
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING;
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback =undefined;
// 失败回调
failCallback = undefined;
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
this.successCallback && this.successCallback(this.value);
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
this.failCallback && this.failCallback(this.reason);
}
then (successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value);
}else if (this.status === REJECTED) {
failCallback(this.reason);
}else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback = successCallback;
this.failCallback = failCallback;
}
}
测试代码:
const MyPromise = require('./myPromise');
let promise = new MyPromise((resolve, reject) =>{
setTimeout(function () {
resolve('成功')
}, 2000)
})
promise.then((value)=>{
console.log(value) // 延迟2s输出成功
},(reason)=>{
console.log(reason)
})
五、promise的then 方法特性
同一个promise的then方法被多次调用
同一个promise下的then方法都是可以被多次调用的,如果我们多次调用同一个promise的then方法时,上述代码就不符合要求了
思路:当多次调用then的时候,也要分为同步和异步的情况,同步的情况是依次执行的(已经知道其状态了),不需要更改,异步情况时我们的then是要将所有传递函数都要执行,所以我们先要将其在then中一次存储起来,那么以前的存放函数的变量就要变成数组,在resolve和reject中一次执行这个参数
class MyPromise{
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING;
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback =[];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
while(this.successCallback.length) this.successCallback.shift()(this.value);
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
while(this.failCallback.length) this.failCallback.shift()(this.reason);
}
then (successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value);
}else if (this.status === REJECTED) {
failCallback(this.reason);
}else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
}
}
测试代码
```javascript
let promise = new MyPromise((resolve, reject) =>{
setTimeout(function () {
resolve('成功')
}, 2000)
})
promise.then((value)=>{
console.log(value)
})
promise.then((value)=>{
console.log(value+1)
})
promise.then((value)=>{
console.log(value+2)
})
promise的then方法被链式调用
promise的then方法是可以被链式调用的,下一个then方法回调函数的参数值实际上是上一个then方法回调函数的返回值。显然上面的代码不适用了
1)、思路:首先分为两个步骤去做:第一,我们要实现then方法的链式调用(首先我们要知道then方法是我们promise下面的方法,要想实现then方法的链式调用,那么每个then方法都应该返回一个promise对象。);第二如何将上一个then方法的返回值传递给下一个回调函数(首先找到下个then所对应的那个promise对象即promise2,在调用promise2中的resolve方法的时候将上一个返回值传递个它)。
class MyPromise{
constructor (executor) {
executor(this.resolve, this.reject)
}
status = PENDING;
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback =[];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
while(this.successCallback.length) this.successCallback.shift()(this.value);
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
while(this.failCallback.length) this.failCallback.shift()(this.reason);
}
then (successCallback, failCallback) {
//既然要返回一个promise对象,那么我们先创建一个promise对象;原先代码去到执行器里面,都是立即执行的
let promise2 = new MyPromise((resolve,reject)=>{
if (this.status === FULFILLED) {
let x = successCallback(this.value);
resolve(x);
}else if (this.status === REJECTED) {
failCallback(this.reason);
}else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
});
return promise2;
}
}
测试代码:
let promise = new MyPromise((resolve, reject) =>{
setTimeout(function () {
resolve('成功')
}, 2000)
})
promise.then((value)=>{
console.log(value)
return 100;
}).then((value)=>{
console.log(value)
})
2)、思路:在then方法的回调函数中return的可以是个普通值也可以是个promise对象。如果是普通值我们可以直接调用resolve方法将这个普通值直接传给下个promise对象;如果返回的是个promise对象的话,我们要去查看这个promise状态,如果是成功的话,要调用resolve方法将这个成功的状态传递给下个promise对象,如果是失败的,我们调用reject方法将这个失败的的状态传递给下个promise对象;所以我们要做下面四件事:
1、判断 x 的值是普通值还是promise对象
2、如果是普通值 直接调用resolve
3、如果是promise对象 查看promsie对象返回的结果
4、再根据promise对象返回的结果 决定调用resolve 还是调用reject
因为我们在成功、失败、等待的时候都要去执行这个事情,所以提取出来resolvePromise(promsie2, x, resolve, reject)
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
// 通过constructor来接收promise的参数也就是执行器
constructor (executor) {
// 下面定义resolve、reject属性,为什么它们是箭头函数呢?因为我们在promise中调用的时候是直接调用的,那么在普通函数中this的
// 指向是window或者undefined,定义成箭头函数目的就是为了这个函数内部的this指向这个类就是这个实例对象也就是promise对象
executor(this.resolve, this.reject)
}
// promsie 状态
status = PENDING; // 这个状态定义成常量而不是直接赋值字符串,是因为常量每次调用的时候代码有提示,而且能复用
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback = [];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
// this.successCallback && this.successCallback(this.value);
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
// this.failCallback && this.failCallback(this.reason);
while(this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
let promsie2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise( x, resolve, reject)
}else if (this.status === REJECTED) {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise( x, resolve, reject)
} else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
});
return promsie2;
}
}
function resolvePromise (x, resolve, reject) {
if (x instanceof MyPromise) {
// promise 对象 调用then来查看是成功还是失败状态,成功的就用resolve传递下去
// x.then(value => resolve(value), reason => reject(reason)); 下面是简化写法
x.then(resolve, reject);
} else {
// 普通值
resolve(x);
}
}
测试
function other () {
return new MyPromise((resolve, reject) {
resolve('other');
})
}
promise.then(value=>{
console.log(value);
return other();
}).then(value=>{
console.log(value);
})
3)、虽然在then方法的回调函数中是可以return一个promise对象的,但有一种方法是例外的,在then的回调函数中是不可以返回当前then它所返回的promise对象的,如果你返回了then方法返回的promise对象,那么这个时候就发生了promise的循环调用,这是程序所不被允许的,会报错。(要根据演示一下系统promise报错情况来封装)
思路:在then中promise2就是我们所返回的promise对象,x是成功回调返回的值,如果这两个值相等,说明返回了自己,就会报错,要将错误信息传递给reject。这个判断我们在resolvePromise进行,所以还要传一个形参,返回的错误是类型错误;这里还有个问题,就是我们传递的参数promise2是在new promise执行完才会存在,而在调用resolvePromise的时候传递是拿不到这个值的,所以我们要将拿值的这一块处理成异步代码,我们可以用setTimeout来变成异步,这里目的是变成异步,而不是延迟,所以时间设置为0.
class MyPromise {
// 通过constructor来接收promise的参数也就是执行器
constructor (executor) {
// 下面定义resolve、reject属性,为什么它们是箭头函数呢?因为我们在promise中调用的时候是直接调用的,那么在普通函数中this的
// 指向是window或者undefined,定义成箭头函数目的就是为了这个函数内部的this指向这个类就是这个实例对象也就是promise对象
executor(this.resolve, this.reject)
}
// promsie 状态
status = PENDING; // 这个状态定义成常量而不是直接赋值字符串,是因为常量每次调用的时候代码有提示,而且能复用
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback = [];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
// this.successCallback && this.successCallback(this.value);
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
// this.failCallback && this.failCallback(this.reason);
while(this.failCallback.length) this.failCallback.shift()(this.reason)
}
then (successCallback, failCallback) {
let promsie2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
setTimeout(() => {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}, 0)
}else if (this.status === REJECTED) {
setTimeout(() => {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}, 0)
} else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
});
return promsie2;
}
}
function resolvePromise (promsie2, x, resolve, reject) {
if (promsie2 === x) {
// 是个类型错误,所以通过new 关键字创建错误
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(value), reason => reject(reason));
x.then(resolve, reject);
} else {
// 普通值
resolve(x);
}
}
测试
let promise = new MyPromise((resolve, reject) =>{
resolve('成功')
})
let p1 = promise.then((value)=>{
console.log(value)
return p1;
});
p1.then((value)=>{
console.log(value)
},(reason)=>{
console.log(reason.message)
})
六、Promise的错误捕获
我们在自己的mypromise中是没有进行任何错误处理的,为了程序的健壮性,我们要去捕获错误。
思路:处理错误;第一:处理的错误是执行器,当执行器执行过程中发生错误的时候,我们让promise的状态变成失败的状态并将错误原因传递下去,我们要在then第二个参数的地方捕获这个错误;第二:我们要处理then方法中的回调函数,如果这个函数执行过程中报错了,我们也要捕获到这个错误,这个错误要在下一个then方法的错误回调中捕获到,如果在执行回调时发生错误,我们要将这个错误通过reject传递给下个去;第三:在异步的执行中也要加上捕获错误
注:如果我们的then方法的失败回调函数中是没有错误的,执行成功的,那么它的返回值会在下个then方法的成功回调中接收
class MyPromise {
// 通过constructor来接收promise的参数也就是执行器
constructor (executor) {
try{
// 下面定义resolve、reject属性,为什么它们是箭头函数呢?因为我们在promise中调用的时候是直接调用的,那么在普通函数中this的
// 指向是window或者undefined,定义成箭头函数目的就是为了这个函数内部的this指向这个类就是这个实例对象也就是promise对象
executor(this.resolve, this.reject)
}catch(e){
this.reject(e);
}
}
// promsie 状态
status = PENDING; // 这个状态定义成常量而不是直接赋值字符串,是因为常量每次调用的时候代码有提示,而且能复用
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback = [];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
// this.successCallback && this.successCallback(this.value);
while(this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
// this.failCallback && this.failCallback(this.reason);
while(this.failCallback.length) this.failCallback.shift()()
}
then (successCallback, failCallback) {
let promsie2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
}else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
} else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
});
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
});
}
});
return promsie2;
}
}
function resolvePromise (promsie2, x, resolve, reject) {
if (promsie2 === x) {
// 是个类型错误,所以通过new 关键字创建错误
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(value), reason => reject(reason));
x.then(resolve, reject);
} else {
// 普通值
resolve(x);
}
}
测试代码:
let promise = new MyPromise((resolve, reject) =>{
setTimeout(()=>{
resolve('成功')
},2000)
})
promise.then((value)=>{
console.log(value)
throw new Error('then error')
},reason=>{
console.log(reason)
}).then((value)=>{
console.log(value)
},(reason)=>{
console.log(reason.message)
})
结果:
七、then方法的参数变成可选参数
我们都知道then方法有两个参数,一个是成功回调一个是失败回调,then方法的这两个参数实际上是可选参数,也就是说在调用then方法的时候我们可以不传递任何参数
如下代码:
var promise = new Promise(function(resolve,reject){
resolve(100)
})
promise
.then()
.then()
.then(value => console.log(value))
思路:如果遇到上面的情况我们应该怎么执行呢?在这种状态下promise的状态会依次向后传递,一直到有回调函数的then方法,也就说第三个then是可以拿到promise状态的
思路:.then() <=>then(value=>value),也就是说我们要看then方法的内部有没有参数,如果没有参数就给他补一个参数,这样就可以向后传递了
class MyPromise {
// 通过constructor来接收promise的参数也就是执行器
constructor (executor) {
try {
// 下面定义resolve、reject属性,为什么它们是箭头函数呢?因为我们在promise中调用的时候是直接调用的,那么在普通函数中this的
// 指向是window或者undefined,定义成箭头函数目的就是为了这个函数内部的this指向这个类就是这个实例对象也就是promise对象
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e);
}
}
// promsie 状态
status = PENDING; // 这个状态定义成常量而不是直接赋值字符串,是因为常量每次调用的时候代码有提示,而且能复用
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback = [];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
// this.successCallback && this.successCallback(this.value);
while(this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
// this.failCallback && this.failCallback(this.reason);
while(this.failCallback.length) this.failCallback.shift()()
}
then (successCallback, failCallback) {
// 参数可选
successCallback = successCallback ? successCallback : value => value;
// 参数可选
failCallback = failCallback ? failCallback: reason => { throw reason };
let promsie2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
}else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
} else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
});
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
});
}
});
return promsie2;
}
}
function resolvePromise (promsie2, x, resolve, reject) {
if (promsie2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(value), reason => reject(reason));
x.then(resolve, reject);
} else {
// 普通值
resolve(x);
}
}
测试代码:
let promise = new MyPromise((resolve, reject) =>{
resolve('成功')
})
promise
.then()
.then()
.then((value)=>{
console.log(value)
}) // 成功
八、promise.all方法实现
promise. all方法解决异步并发问题,允许我们以异步代码调用的顺序得到异步代码执行的结果,a
ll方法中先传入谁,谁先调用。以数组作为参数,数组中可以填入任何值,包括普通值和promise对象,数组中值的顺序一定是我们得到结果的顺序。这个方法返回值也是个promise对象。有个特点,在all方法中所有的promise对象如果状态都是成功的,那么all方法最后的结果也是成功的,如果有一个失败最后结果也是失败的
思路:怎么调用的?一个类调用all方法,所以all方法一定是个静态方法,第一:返回一个promise对象,第二:循环数组,看是普通值还是promise对象,如果是普通值就放在结果数组中,如果是promise对象那么我们先执行promise对象,然后把结果放在结果数组中,是否是MyPromise类下的一个实例;封的这个all方法有个问题,for循环是一瞬间就执行完的,但是在这过程中参与了异步操作,我们要等待所以异步完成之后,才能调用resolve这个方法,为了解决这个问题,我们定义了一个index来记录执行了多少次addDate,与数组长度相等,才执行resolve这个方法
static all (array) {
let result = [];
let index = 0;
return new MyPromise((resolve, reject) => {
function addData (key, value) {
result[key] = value;
index++;
if (index === array.length) {
resolve(result);
}
}
for (let i = 0; i < array.length; i++) {
let current = array[i];
if (current instanceof MyPromise) {
// promise 对象
current.then(value => addData(i, value), reason => reject(reason))
}else {
// 普通值
addData(i, array[i]);
}
}
})
}
九、Promise.resolve 方法的实现
特性:
1)、promise.resolve方法:将给定的值转化成promise对象,也就是说它的返回值就是一个promise对象
2)、如果参数是个固定的值,会将这个值作为参数传给then方法,如果参数是个promise对象,会将这个它原封不动的返回出去,供后面的then调用
原理:
判断一下这个参数是否是个promise对象,如果是原封不动的返回,如果不是就创建一个promise对象,将给定的值包裹在promise中,再将这个值返回就可以了
static resolve (value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
十、Promise.finally 方法的实现
特性:
1)、promise.finally方法有两个特点,无论promise对象最后的状态是成功的还是失败的,finally中的回调函数始终都会执行一次,另一个就是在finally方法的后面可以调用then方法来拿到promise最终返回的结果
2)、这个方法不是静态方法,是定义在类的原型对象身上
思路:在finally的内部我们怎么知道这个promise的状态呢,then是知道的,所以调用then方法;系统提供的promise中是等待finally中return的promise执行完之后在执行后面的then方法,这时候需要借助resolve这个方法,看finally的参数执行完之后返回的是什么,如果是普通值,转换成promise对象,等待执行完毕,如果是promise对象,还是等待完成,返回这个value;
finally (callback) {
return this.then(value => {
return MyPromise.resolve(callback()).then(() => value);
}, reason => {
return MyPromise.resolve(callback()).then(() => { throw reason })
})
}
十一、Promise.catch 方法的实现
特性:
1)、catch 方法是用来处理当前promise对象失败的情况的,也就是说then方法是可以不传递失败回调的,如果不传递失败回调,这个失败回调就会被catch方法所捕获。
2)、catch也是定义在原型对象上的
3)、因为这个catch方法后面也是可以继续链式调用的,所以要return出去
4)、实际上内部调用的then方法,只不过没有注册成功回调,只注册了失败回调
原理:只需在catch方法内部调用then方法就可以了,在调用then方法的时候在成功的那个地方传入undefined,在失败的那个地方传递回调函数,
catch (failCallback) {
return this.then(undefined, failCallback)
}
十二、小结
手写完整Promise代码
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
// 通过constructor来接收promise的参数也就是执行器
constructor (executor) {
try {
// 下面定义resolve、reject属性,为什么它们是箭头函数呢?因为我们在promise中调用的时候是直接调用的,那么在普通函数中this的
// 指向是window或者undefined,定义成箭头函数目的就是为了这个函数内部的this指向这个类就是这个实例对象也就是promise对象
executor(this.resolve, this.reject)
} catch (e) {
this.reject(e);
}
}
// promsie 状态
status = PENDING; // 这个状态定义成常量而不是直接赋值字符串,是因为常量每次调用的时候代码有提示,而且能复用
// 成功之后的值(默认值是undefined)
value = undefined;
// 失败后的原因
reason = undefined;
// 成功回调
successCallback = [];
// 失败回调
failCallback = [];
resolve = value => {
// 如果状态不是等待 阻止程序向下执行(因为一旦状态确定就不可更改)
if (this.status !== PENDING) return;
// 将状态更改为成功
this.status = FULFILLED;
// 保存成功之后的值 为什么要保存?因为要在then中拿到
this.value = value;
// 判断成功回调是否存在 如果存在 调用
// this.successCallback && this.successCallback(this.value);
while(this.successCallback.length) this.successCallback.shift()()
}
reject = reason => {
// 如果状态不是等待 阻止程序向下执行
if (this.status !== PENDING) return;
// 将状态更改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// 判断失败回调是否存在 如果存在 调用
// this.failCallback && this.failCallback(this.reason);
while(this.failCallback.length) this.failCallback.shift()()
}
then (successCallback, failCallback) {
// 参数可选
successCallback = successCallback ? successCallback : value => value;
// 参数可选
failCallback = failCallback ? failCallback: reason => { throw reason };
let promsie2 = new MyPromise((resolve, reject) => {
// 判断状态
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
}else if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
} else {
// 等待
// 将成功回调和失败回调存储起来
this.successCallback.push(() => {
setTimeout(() => {
try {
let x = successCallback(this.value);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
});
this.failCallback.push(() => {
setTimeout(() => {
try {
let x = failCallback(this.reason);
// 判断 x 的值是普通值还是promise对象
// 如果是普通值 直接调用resolve
// 如果是promise对象 查看promsie对象返回的结果
// 再根据promise对象返回的结果 决定调用resolve 还是调用reject
resolvePromise(promsie2, x, resolve, reject)
}catch (e) {
reject(e);
}
}, 0)
});
}
});
return promsie2;
}
finally (callback) {
return this.then(value => {
return MyPromise.resolve(callback()).then(() => value);
}, reason => {
return MyPromise.resolve(callback()).then(() => { throw reason })
})
}
catch (failCallback) {
return this.then(undefined, failCallback)
}
static all (array) {
let result = [];
let index = 0;
return new MyPromise((resolve, reject) => {
function addData (key, value) {
result[key] = value;
index++;
if (index === array.length) {
resolve(result);
}
}
for (let i = 0; i < array.length; i++) {
let current = array[i];
if (current instanceof MyPromise) {
// promise 对象
current.then(value => addData(i, value), reason => reject(reason))
}else {
// 普通值
addData(i, array[i]);
}
}
})
}
static resolve (value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
}
function resolvePromise (promsie2, x, resolve, reject) {
if (promsie2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if (x instanceof MyPromise) {
// promise 对象
// x.then(value => resolve(value), reason => reject(reason));
x.then(resolve, reject);
} else {
// 普通值
resolve(x);
}
}
- Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行
- Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
pending -> fulfilled
pending -> rejected
一旦状态确定就不可更改 - resolve和reject函数是用来更改状态的
resolve: fulfilled
reject: rejected - then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的(因为每个promise都可以调用到then方法)
- then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因
- 同一个promise对象下面的then方法是可以被调用多次的
- then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
- then方法返回的promise对象和调用的对象,不能是同一个
- promise的错误捕获:执行器、回调函数、异步中。如果then方法的失败回调函数中是没有错误的,执行成功的,那么它的返回值会在下个then方法的成功回调中接收
- then方法的参数是可选参数,promise的状态会依次向后传递,一直到有回调函数的then方法
- promise.all方法:解决异步并发问题
- promise.resolve方法:将给定的值转化成promise对象
- promise.finally方法有两个特点,无论promise对象最后的状态是成功的还是失败的,finally中的回调函数始终都会执行一次,另一个就是在finally方法的后面可以调用then方法来拿到promise最终返回的结果
- catch 方法是用来处理当前promise对象失败的情况的,也就是说then方法是可以不传递失败回调的,如果不传递失败回调,这个失败回调就会被catch方法所捕获