promise是一个管理异步编程的方案,它是一个构造函数,每次使用可用new创建实例;它有三种状态:pending、fulfilled和rejected,这三种状态不会受外界影响,状态只能由pending变为fullfilled(成功),pending变为rejected(失败),且一旦改变就不会再改变,在状态改变后,它会返回成功的结果或者失败的原因,它对外抛出了resolve、reject、catch、finally、then、all、race、done,在最新的提案中,添加了allsettled方法,它不管成功、失败都会返回,接下来,我们自己实现整个promise
executor函数
我们知道,在创建一个promise实例时,都会立即执行executor函数,executor函数传递两个参数,resolve和reject,如果executor函数执行错误,promise实例状态会变为rejected
class mypromise{ constructor(executor) { this.status = "pending"; // 初始化状态为pending this.value = undefined; // 初始化返回的成功的结果或者失败的原因 // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { if(this.status !== "pending") return; // 状态一旦改变,就不会再变 this.status = "resolved"; this.value = result; } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { if(this.status !== "pending") return; this.status = "rejected"; this.value = reason; } // try、catch捕获异常,如果错误,执行reject方法 try { executor(resolve, reject) } catch(err) { reject(err) } }}
我们来验证一下,现在的promise是什么样的
let p1 = new mypromise((resolve, reject) => { resolve(1);})let p2 = new mypromise((resolve, reject) => { reject(2);})console.log(p1);console.log(p2);
可以看到,状态已经改变了,里面的值也是成功的结果和失败的原因。then方法有两个参数,第一个参数是成功时执行的,第二个参数为失败后执行的,then的链式调用和数组等是一样的,每次执行后会返回一个promise实例。如果成功后,第一个then中成功的函数为null,它会继续向下查找,直至不为null的函数执行,上一个then中返回的结果会直接影响下一个then中执行成功或者失败的哪个函数,了解了这些之后,我们尝试实现一下~
then方法
then(resolvefn, rejectfn) { // 如果传入的两个参数不是函数,则直接执行返回结果 let resolvearr = []; let rejectarr = []; if(typeof resolvefn !== "function") { resolvefn = result => { return result; } } if(typeof rejectfn !== "function") { rejectfn = reason => { return mypromise.reject(reason); } } return new mypromise((resolve, reject) => { resolvearr.push(result => { try { let x = resolvefn(result); if(x instanceof mypromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) rejectarr.push(reason => { try { let x = rejectfn(reason); if(x instanceof mypromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) })}
我们来整理一下上面的代码
class mypromise{ constructor(executor) { this.status = "pending"; // 初始化状态为pending this.value = undefined; // 初始化返回的成功的结果或者失败的原因 this.resolvearr = []; // 初始化then中成功的方法 this.rejectarr = []; // 初始化then中失败的方法 // 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多 let change = (status, value) => { if(this.status !== "pending") return; // 状态一旦改变,就不会再变 this.status = status; this.value = value; // 根据状态判断要执行成功的方法或失败的方法 let fnarr = status === "resolved" ? this.resolvearr : this.rejectarr; // fnarr中的方法依次执行 fnarr.foreach(item => { if(typeof item !== "function") return; item(this. value); }) } // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { change("resolved", result) } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { change("rejected", reason); } // try、catch捕获异常,如果错误,执行reject方法 try { executor(resolve, reject) } catch(err) { reject(err) } } then(resolvefn, rejectfn) { // 如果传入的两个参数不是函数,则直接执行返回结果 if(typeof resolvefn !== "function") { resolvefn = result => { return result; } } if(typeof rejectfn !== "function") { rejectfn = reason => { return mypromise.reject(reason); } } return new mypromise((resolve, reject) => { this.resolvearr.push(result => { try { let x = resolvefn(result); // 获取执行成功方法返回的结果 // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现 if(x instanceof mypromise) { x.then(resolve, reject) return; } // 不是promise实例,直接执行成功的方法 resolve(x); } catch(err) { reject(err) } }) this.rejectarr.push(reason => { try { let x = rejectfn(reason); if(x instanceof mypromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) }) }}
我们来看一下效果
new mypromise((resolve, reject) => { resolve(1);}).then(res => { console.log(res, 'success');}, err => { console.log(err, 'error');})
这时候,问题出现了,我们发现好像什么也没有输出,如果我们对上面的测试例子做一下小小的改动呢?
new mypromise((resolve, reject) => { settimeout(_ => { resolve(1); }, 0)}).then(res => { console.log(res, 'success'); // 1 "success"}, err => { console.log(err, 'error');})
这是因为创建了promise实例就立即执行了executor函数,还没有执行then方法,那么不管成功还是失败的数组中,都是空的。那可能小伙伴们又有疑问了,为什么加了settimeout就好使了呢?这是因为在事件队列机制中,settimeout会放入事件队列中,等主线程执行完成后再执行,此时then方法会存储成功或者失败的函数,所以不管是成功的数组还是失败的数组中都已经有值了,这个时候再去执行就完全了~
但是我们不能在使用的时候写settimeout当做解决方案呀,既然我们在封装,就要在封装的函数内解决问题,按照这样的思路,我们也同样可以在resolve和reject方法执行的时候,判断数组中是否有值,如果没有,我们可以利用settimeout让它延后执行,代码如下~
// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回let resolve = result => { // 如果数组中有值,则立即改变状态 if(this.resolvearr.length > 0) { change("resolved", result) } // 如果没值,则延后改变状态 let timer = settimeout(_ => { change("resolved", result) cleartimeout(timer); }, 0)}// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回let reject = reason => {// 如果数组中有值,则立即改变状态 if(this.rejectarr.length > 0) { change("rejected", reason); } // 如果没值,则延后改变状态 let timer = settimeout(_ => { change("rejected", reason); cleartimeout(timer); }, 0)}
现在我们再试一下
// 1、已经成功了new mypromise((resolve, reject) => { resolve('我成功啦,吼吼吼~~~~'); reject('我都已经成功了,你别想让我失败,哼~~');}).then(res => { console.log(res, 'success'); // 我成功啦,吼吼吼~~~~ success}, err => { console.log(err, 'error');})// 2、先失败了new mypromise((resolve, reject) => { reject('失败了,我好委屈,呜呜呜~~'); resolve('已经失败了~~~'); }).then(res => { console.log(res, 'success'); }, err => { console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error})// 3、链式调用new mypromise((resolve, reject) => { reject('失败了,我好委屈,呜呜呜~~'); resolve('已经失败了~~~'); }).then(res => { console.log(res);}, err => { console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error return '我要发奋图强,不会被困难所击倒,我要成功!!!'}).then(res1 => { console.log(res1, '经过不懈努力,我终于在第二次成功了~'); // 我要发奋图强,不会被困难所击倒,我要成功!!! 经过不懈努力,我终于在第二次成功了~}, err1 => { console.log(err1, '第二次失败');})
这就完美解决了第一次调用,不会执行then方法的问题。同时,实现了链式的调用。对于链式的调用,我多啰嗦两句,其实不管是数组的链式调用,都是因为上一次返回的还是此实例。
catch方法
catch方法是捕获异常,它和then方法的第二个回调函数是一样的
catch(rejectfn) { return this.then(null, rejectfn)}
resolve方法
我们知道,promsie也可以这样用
let p1 = mypromise.resolve(1);console.log(p1);
我们期望有这样一种写法,但是现在肯定会抛出错误:mypromise.resolve不是一个方法
现在需要我们封装一下resolve方法,我们需要明确的是,resolve之后,promise是支持再继续链式调用then的,所以,我们需要执行resolve方法,返回一个promise实例
static resolve(result) { // 返回新的promise实例,执行promise实例中resolve方法 return new mypromise(resolve => { resolve(result) })}
reject方法
像resolve方法一样,只不过它接收的是失败的函数
static reject(reason) { // 返回新的promise实例,执行promise实例中reject方法 return new mypromise((_, reject) => { reject(reason); })}
done方法
es6标准入门一书中,对done方法的解释是这样的:无论promise对象的回调链以then方法还是catch方法结尾,只要最后一个方法抛出错误,都有可能无法捕获到。为此,promise提供了一个done方法,它总是处于回掉链的尾端,保证抛出任何可能出现的错误。好了,我们知道了这个方法是干啥的,现在就开始写吧~
done(resolvefn, rejectfn) { this.then(resolvefn, rejectfn) .catch(reason => { settimeout(() => { throw reason; }, 0) })}
它可以接收fulfilled、rejected状态的回调函数,也可以不提供任何参数。但是无论怎样,done方法都会捕捉到任何可能出现的错误,并向全局抛出
finally方法
finally方法是无论成功还是失败都会执行的方法,像这样的方法还有小程序中的complete方法等等,我们来尝试实现一下~
finally(finallyfn) { let p = this.constructor; return this.then( value => p.resolve(finallyfn()).then(() => value), reason => p.reject(finallyfn()).then(() => reason) )}
我们来验证一下
new mypromise((resolve, reject) => { reject('失败了,我好委屈,呜呜呜~~'); resolve('已经失败了~~~');}).then(res => { console.log(res);}, err => { console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error return '我要发奋图强,不会被困难所击倒,我要成功!!!'}).finally(() => { console.log('执行了吗'); // 这里会输出"执行了吗"})
all方法
all方法接收一个数组,当数组中每个实例都成功时才会返回,返回的也是一个数组,每个参数为对应的promise返回的结果,如果有一项失败了,all方法都会返回失败
// 接收数组参数static all(promiselist) { // 返回新实例,调用后还可使用then、catch等方法 return new mypromise((resolve, reject) => { let index = 0, // 成功次数计数 results = []; // 返回的结果 for(let i = 0; i < promiselist.length; i++) { let item = promiselist[i]; // 如果item不是promise实例 if(!(item instanceof mypromise)) return; item.then(result => { index++; results[i] = result; if(index === promiselist.length) { resolve(results); } }).catch(reason => { reject(reason); }) } })}
来验证一下
// 1.有失败的情况let p1 = mypromise.resolve(1);let p2 = mypromise.reject(2);let p3 = mypromise.resolve(3);mypromise.all([p1, p2, p3]) .then(res => { console.log(res); }).catch(err => { console.log(err, 'err'); // 2 "err" })// 2.无失败的情况let p1 = mypromise.resolve(1);let p2 = mypromise.resolve(2);let p3 = mypromise.resolve(3);mypromise.all([p1, p2, p3]) .then(res => { console.log(res, 'success'); // [1, 2, 3] "success" }).catch(err => { console.log(err, 'err'); })
race方法
race方法同样接收一个数组参数,里面每一项是promise实例,它返回最快改变状态的promise实例方法的结果
static race(promiselist) { return new mypromise((resolve, reject) => { promiselist.foreach(item => { if(!(item instanceof mypromise)) return; item.then(result => { resolve(result); }).catch(err => { reject(err) }) }) })}复制代码验证// 1.let p1 = mypromise.resolve(1);let p2 = mypromise.reject(2);let p3 = mypromise.resolve(3);mypromise.race([p1, p2, p3]) .then(res => { console.log(res); // 1 'success' }).catch(err => { console.log(err, 'err'); })// 2.let p1 = mypromise.reject(1);let p2 = mypromise.resolve(2);let p3 = mypromise.resolve(3);mypromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); }).catch(err => { console.log(err, 'err'); // 1 'err' }) // 3.let p1 = mypromise.reject(1);let p2 = mypromise.reject(2);let p3 = mypromise.reject(3);mypromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); }).catch(err => { console.log(err, 'err'); // 1 'err' })
尝试实现allsettled方法
allsettled方法也是接收数组参数,但是它无论成功或者失败,都会返回
static allsettled(promiselist) { return new mypromise((resolve, reject) => { let results = []; for(let i = 0; i < promiselist.length; i++) { let item = promiselist[i]; if(!(item instanceof mypromise)) return; item.then(result => { results[i] = result; }, reason => { results[i] = reason; }) resolve(results); } })}复制代码验证// 1.let p1 = mypromise.resolve(1);let p2 = mypromise.resolve(2);let p3 = mypromise.resolve(3);mypromise.race([p1, p2, p3]) .then(res => { console.log(res); // [1, 2, 3] 'success' }).catch(err => { console.log(err, 'err'); })// 2.let p1 = mypromise.reject(1);let p2 = mypromise.reject(2);let p3 = mypromise.reject(3);mypromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); // [1, 2, 3] 'success' }).catch(err => { console.log(err, 'err'); }) // 3.let p1 = mypromise.resolve(1);let p2 = mypromise.reject(2);let p3 = mypromise.resolve(3);mypromise.race([p1, p2, p3]) .then(res => { console.log(res, 'success'); // [1, 2, 3] 'success' }).catch(err => { console.log(err, 'err'); })
推荐教程:《js教程》
以上就是手写js实现promise的详细内容。
