Promise源码实现
前言
自从ECMAScript 2015(es6)问世以来javascript就不仅仅是一个简略脚本语言了真正的成为了一种能够用于企业级开发的计算机高级程序设计语言,es6新增了很多货色,能够算的上对整个javascript语言进行了重构。其中最重要的之一当属promsie。此文章因为笔者程度无限,仍然存在很多有余。只是实现了promise的基本功能,也没有齐全依照Promise/A+标准来实现。然而能够为咱们提供一个程序设计的思路,开辟咱们的思维。在浏览此文章之前心愿您对promise的根本用法有所理解,并且把握公布订阅设计模式,递归,以及函数式编程等思维。闲言少序让咱们开始吧!
代码实现
1.根本架构
//1. 3种状态 pending fulfilled rejected 三种状态 //2.链式调用 then //3.executon 执行器函数 这个函数是同步执行的 //此时此刻executon函数是实际上是理论参数,咱们在实例化promise的时候传进去的resolve和reject才是模式 参数 :重点 //上述是promise的外围三大概念 //咱们须要明确的一点是 new Promsie 肯定是通过构造函数或者 class关键字去定义一个类,在这里咱们应用类 的模式去写class MyPromise{ //executon函数就是咱们实例化promsie传入的回调函数,作为主体函数 constructor(executon){ this.state = 'pending'//这个用于保留咱们promise的三种状态 默认是pending即期待状态 this.value = undefined//这是咱们最终胜利返回的一个promise执行后果的值 this.reason = undefined//这是咱们执行谬误最终返回的一个值//在执行胜利的函数时候咱们首先能够做的就是扭转promise的状态,接管一个参数,并把这个value赋值给以后类的value let resolve = (value)=>{ if(this.state === 'pending'){ this.state='fullFilled' this.value = value } } //同理在执行失败的函数中咱们也要扭转promise的状态并对谬误进行赋值 let reject=(reason)=>{ if(this.state === 'pending'){ this.state = 'rejected' this.reason = reason } } //主体函数 executon(resolve,reject) } //并且这两个参数都是回调函数,javascript弱小之处体现的一点就在于反对函数式编程所谓的函数式编程感兴趣的同学能够上来钻研一下,这里对应着就是then办法外面胜利的回调和失败的回调 then(onFullFilled,onRejected){ //这里呢咱们也须要判断以后的状态 //当咱们的状态变成胜利的时候咱们须要执行then外面胜利的回调函数 if(this.state === 'fullFilled'){ onFullFilled(this.value) } //同理当状态变成失败的时候咱们也要执行失败的回调函数,并把失败的值穿进去 if(this.state === 'rejected'){ onRejected(this.reason) } }}
1.1测试用例1
//此时基础架构以及搭建好了咱们来试试 //咱们能够在这里剖析一下整个执行过程 //1.当咱们实例化这个myPromise的时候咱们应用了resolve去吧状态改成fullFilled所以咱们then外面胜利的 函数则会执行 //2.resolve此时是一个形式参数来接管executon函数外部执行后果 executon执行结束才会执行咱们的resolv e所有大家能够看到 //3.只管resolve先执行 然而自身这是一个回调函数所以仍然是先打印1再打印10 从一种不谨严的角度登程 //这里能够了解成微工作new MyPromise((resolve, reject) => { resolve(10) conssole.log(1)//1 }).then((res) => { console.log(res)//10 })
上述代码以及测试用例都能通过了,下一步咱们将解决一下谬误捕捉,promise外部呈现任何谬误都会被捕捉到,从而不会阻塞代码的执行,上面间接上代码
class MyPromise{ constructor(executon){ this.state = 'pending' this.value = undefined this.reason = undefined let resolve = (value)=>{ if(this.state === 'pending'){ this.state='fullFilled' this.value = value } } let reject=(reason)=>{ if(this.state === 'pending'){ this.state = 'rejected' this.reason = reason } } //捕捉谬误次要是在这里捕捉 try { executon(resolve,reject) } catch (error) { //抛出谬误 reject(error) } } then(onFullFilled,onRejected){ if(this.state === 'fullFilled'){ onFullFilled(this.value) } if(this.state === 'rejected'){ onRejected(this.reason) } }}
1.2测试用例2
new MyPromise((resolve, reject) => { console.log(a)//一个未定义的a必然出错 reject(10) }).then((res) => { console.log(res)//这里没有打印 }, (reason) => { console.log(reason, 'error')//这里捕捉到了谬误 })
2解决异步工作
当初咱们的myPromise曾经有了根本的架构,然而并不具备异步的解决能力,promise的诞生就是为了解决javascript传统的应用回调函数去承受一个异步工作处理结果的缺点,这种缺点就是咱们陈词滥调的回调天堂。
那么promise外部到底是怎么做的能够防止陷入回调天堂呢?答案是采纳公布订阅者设计模式,这种设计模式的实质就是存在一种一对多的依赖关系,不理解这种设计模式的同学,能够查一查资料尝试写一写。上面咱们间接上代码
class MyPromise{ constructor(executon){ this.state = 'pending' this.value = undefined this.reason = undefined //利用公布订阅设计模式在适合的机会去执行注册的回调函数即在异步的回调函数执行实现咱们再执行then外面的代码 //既然是公布订阅那么咱们则须要两个缓存列表(缓存对应的回调函数)一个胜利的列表 一个失败的列表 this.onResolveCallback = []//胜利 this.onRejectedCallback = []//失败 let resolve = (value)=>{ if(this.state === 'pending'){ this.state='fullFilled' this.value = value //重写resolve办法咱们须要在适合的机会去执行异步对应的回调函数 //这里能够了解为公布订阅外面的订阅者 this.onResolveCallback.forEach(fn=>{fn()}) } } let reject=(reason)=>{ if(this.state === 'pending'){ this.state = 'rejected' this.reason = reason //同理reject也是须要去执行对应的函数 this.onRejectedCallback.forEach(fn=>{fn()}) } } try { executon(resolve,reject) } catch (error) { reject(error) } } then(onFullFilled,onRejected){ if(this.state === 'fullFilled'){ onFullFilled(this.value) } if(this.state === 'rejected'){ onRejected(this.reason) } //如果咱们在实例化的时候向executon函数加上一个异步工作那么此时咱们的状态将永远是期待状态 //因为,异步会后于同步代码执行,咱们的胜利回调也就是resolve还没执行,then办法外面的判断胜利也不会 执行,那么状态将永远是期待状态所有咱们须要在这里判断一下如果呈现异步的状况,那么咱们的状态将永远 是期待状态 if(this.state === 'pending'){ //如果是期待状态咱们须要向咱们缓存列表外面注册胜利回调函数,在将来的某个时刻去执行他们 //这里也能够了解为公布带订阅外面的发布者 this.onResolveCallback.push(()=>{ onFullFilled(this.value)//传入胜利的 }) //同理如果是失败的状况也要注册一个失败的回调函数 this.onRejectedCallback.push(()=>{ onRejected(this.reason) }) } }}
2.1 测试用例3 上述代码为咱们的myPromise增加了解决异步工作的能力,那么就要补上咱们的测试
let p1 = new MyPromise((resolve, reject) => { //请留神 setTimeout家喻户晓是异步的而且是一个宏工作是咱们的的浏览器提供的api。setTimeout会在所有的同步代码后执行 //我么能够剖析一下此时代码的执行过程,当咱们实例化的时候遇到了异步,那么咱们状态就不会失去扭转,这时咱们在myPromise的then办法外面回去注册胜利的回调函数,当1s过后咱们的 resolve函数执行,那么状态就会产生扭转,此时咱们缓存列表对应的回调也会执行,这里十分绕,很难了解,手动写一写将会有助于了解 setTimeout(() => { resolve(100) },1000) }) p1.then((res) => { console.log(res + 1)//1s后 打印101测试通过 })
2.2测试用例4 因为咱们应用了缓存列表所有每一个then都能捕捉到异步都执行同样的,咱们也补上测试用例
let p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(100) }.1000) }) p1.then((res) => { console.log(res + 1)//1s 打印101 }) p1.then((res) => { console.log(res + 2)//1s后 打印102 })
3.实现链式调用
当初最简单,最绕,最硬核,最重要的链式调用要来了,链式调用是promsie的外围,重中之重。这一段十分难以了解,笔者写的也不是很分明,还请多多包涵,如果想要弄得这里还是那句话得入手写一写。上代码
class MyPromise { constructor(executon) { this.state = 'pending' this.value = undefined this.reason = undefined this.onResolveCallback = [] this.onRejectedCallback = [] let resolve = (value) => { if (this.state === 'pending') { this.state = 'fullFilled' this.value = value this.onResolveCallback.forEach(fn => { fn() }) } } let reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected' this.reason = reason this.onRejectedCallback.forEach(fn => { fn() }) } } try { executon(resolve, reject) } catch (error) { reject(error) } } //链式调用 :重点 //这里实际上也是接管的形式参数 onFullFilled onRejected //并且这两个参数都是回调函数,javascript弱小之处体现的一点就在于反对函数式编程所谓的函数式编程感兴趣的同学能够上来钻研一下 then(onFullFilled, onRejected) { //实现链式调用咱们的then外面要默认返回一个新的promsie //留神思考一下这里为什么不能间接return this? 因为这里的this实际上是以后的proise 实例 //所以要返回一个新的promsie 这里十分的绕啊 我给出论断是 咱们then多少次那么这里的 代码就 会执行多少次 //所以这里实际上是一个递归 //并且我门须要解决如果promise实例外面的then也返回的是一个新的promise实例应该怎么做? //实质上就是依据x的值来解决不同的后果 定义一个resolvePromsie函数来执行下一步 let p2 = new MyPromise((resolve, reject) => { let x;//记录一下 咱们能够想一想这个x到底是什么货色?其实就是一个用于记录then外面返回的后果,十分难以了解啊这里 //这里呢咱们也须要判断以后的状态 if (this.state === 'fullFilled') { //并且传入以后的value值,很重要的一点是 此时的this指向的myclass这个类即指向结构函 数,这里的this.value是一个理论参数 //并且这里肯定要走异步否自状态永远是pending状态因为then外面也可能是实例化了一个新 的promise并且在外面做了一个异步工作 setTimeout(()=>{ x = onFullFilled(this.value) resolvePromsie(p2,x,resolve,reject) }) } if (this.state === 'rejected') { x=onRejected(this.reason) resolvePromsie(p2,x,resolve,reject) } if (this.state === 'pending') { this.onResolveCallback.push(() => { x=onFullFilled(this.value) resolvePromsie(p2,x,resolve,reject) }) this.onRejectedCallback.push(() => { x=onRejected(this.reason) resolvePromsie(p2,x,resolve,reject) }) } }) //return这个实例进来 return p2 } } //定义一个执行下一步操作的函数这里次要是接管了四个参数 //p2 一个promise的实例,在标准外面then办法会默认返回一个新的promise实例 留神这里是新的 function resolvePromsie(p2,x,resolve,reject){ //请留神p2和x不能是同一个值至于为什么能够思考一下 或者依据promise A+规范去了解 if(x === p2){ return new Error('援用谬误') } //为了兼容其余promise的库咱们这里做了一些兼容 x能够是一个新的实例也能够是一个函数 if(typeof x === 'object' && x!== null || typeof x=== 'function'){ //谬误捕捉 try { //x如果是一个新的promise实例那么它必然有then办法 let then = x.then if(typeof then === 'function'){ //如果存在then间接让x调用 then.call(x,y=>{ //胜利的回调 //resolve(y) 留神这里如果仅仅是调用resolve(y)那么只是判断了第二层的回调函数第二层往下便执行不了 //所以这里必须要递归执行resolvePromsie 无论有多少promise嵌套多少实例都能监听到 resolvePromsie(p2,y,resolve,reject) },err=>{ //失败的回调 失败就间接抛出谬误无需递归 reject(err) }) } } catch (error) { reject(err) } }else{ //如果x是一个一般值则间接调用胜利即可 resolve(x) } }
3.1 测试用例5 上述代码曾经为咱们补上了 链式调用曾经如果咱们在then外面从新实例化一个promse的处理结果 批准咱们补上测试用例
let p1 = new MyPromise((resolve, reject) => { resolve(100) }) p1.then((res) => { console.log(res + 1)//101 return res+4 }).then((res)=>{ console.log(res)//102 })
3.2测试用例 6 异步链式调用
let p1 = new MyPromise((resolve, reject) => { setTimeout(()=>{ resolve(100) }) }) p1.then((res) => { console.log(res + 1)//101 return res+4 }).then((res)=>{ console.log(res)//104 })
3.3 测试用例7 then外面返回一个新的promise实例
let p1 = new MyPromise((resolve, reject) => { setTimeout(()=>{ resolve(100) }) }) p1.then((res) => { console.log(res)//100 return new MyPromise((resolve,reject)=>{ resolve(200) }) }).then((res)=>{ console.log(res)//200 })
3.4 测试用例8 程序执行异步工作 这也是promise的外围 即解决回调天堂
let p1 = new MyPromise((resolve, reject) => { setTimeout(()=>{ console.log(1)// 3s后打印1 resolve() },3000) }) p1.then((res) => { return new MyPromise((resolve,reject)=>{ setTimeout(()=>{ console.log(2)// 函数体外面的异步执行实现后 等2s 打印2 resolve() },2000) }) }).then((res)=>{ console.log(3)//最初是3 }) //这里是整个promise的外围 同步的形式去执行异步工作
结语
手写一个promise并非一件很容易的事,此案例还有很多办法没有实现,感兴趣的同学能够再次根底之上实现一下all,race,finally等办法,程序自身就是一件很形象的事,只能在一次又一次的实际中锤炼本人的思维。作者实力无限,还望斧正。