前言:javascript
一直以来都是只会调用Promise的API,并且调API仍是调用axios封装好的Promise,太丢人了!!!没有真正的去了解过它的原理是如何实现的,本身也看过不少博主实现的Promise,但总以为用原型链的OOP晦涩难懂。java
我的的理解:若是带着观察者模式的想法来理解Promise源码,你就会发现Promise自己其实一种微任务的观察者模式,一个异步任务的完成,res/rej
的状态回调hook => 通知全部then()
订阅的promise对象。promise只是将观察者模式运用到微任务。让promise对象可以具备很高的优先级。说到底仍是一种解藕的设计模式。jquery
在了解Promise以前,我以为有必要去了解一下Promise诞生的缘由。 直接就那上面的axios来讲吧,之前没有出现axios的时候,你们是怎么去与后台接口作交互的呢? 我当时是用jQuery封装好的AJAX去作的。下面有一个例子ios
$.ajax({
type: 'POST', //GET or POST url: "jquery-ajax", cache: false, data: {todo:"ajaxexample1"}, success: functionSucceed, error: functionFailed, statusCode: { 404: function() { alert("page not found"); } } });
若是是单独的一个请求还好,可是若是得发送两个相互依赖的请求呢?这时候就会出现回调地狱的问题,不能自拔。如下就是一个简单的例子。git
a(function (result1) { b(result1,function (result2) { c(result2, function (result3) { d(result3, function (result4) { e(result4, function (result5) { console.log(result5) }) }) }) }) })
假如说让你去维护一个这样的代码... 惧怕的兄弟萌把惧怕打在评论区[doge]。上面的代码有什么问题呢?es6
问题出来了,那解决的思路也有了:github
设计一个对象实现上面两个功能,使用TypeScript的OOP相比于用原型链来实现会更加的容易理解。在实现Promise源码以前,对于Promise的用法、基本定义必定要有一个全方面的认知,否则去了解Promise也艰深晦涩。能够先去看看MDN对于Promise的本质定义ajax
Promise有三种状态:pending、 resolve、reject 对应了 等待、成功、失败,表示一个异步任务的状态是怎么样的。axios
enum States { PENDING = 'PENDING', RESOLVED = 'RESOLVED', REJECTED = 'REJECTED' } class MyPromise { private state: States = States.PENDING; private handlers: any[] = []; private value: any; constructor(executor: (resolve, reject) => void) { try { executor(this.resolve, this.reject); } catch (e) { this.reject(e); } } }
handlers数组是表示当调用了then()方法时,向handlers添加回调函数。好比如下的状况,handlers中就会有两个回调函数,等待Promise的resolve/reject设置状态以后,调用handlers里的全部回调函数。设计模式
let promise1 = new MyPromise(test); let promise2 = promise1 .then(res => { // <=== 匿名回调函数 console.log(res); return 2; }); let promise3 = promise1 .then(res => { // <=== 匿名回调函数 setTimeout(() => { console.log(res + '***********************'); return 4; }, 1000); })
value表示的一个异步函数返回值。
executor是带有 resolve
和 reject
两个参数的函数 。Promise构造函数执行时当即调用executor
函数, resolve
和 reject
两个函数做为参数传递给executor
(executor 函数在Promise构造函数返回所建promise实例对象前被调用)
回到主题,我以为先介绍then()方法是如何实现的比较合适
then(onSuccess?, onFail?) {
return new MyPromise((resolve, reject) => { return this.attachHandler({ onSuccess: result => { if (!onSuccess) return resolve(result); try { return resolve(onSuccess(result)); } catch (e) { return reject(e); } }, onFail: reason => { if (!onFail) return reject(reason); try { return resolve(onFail(reason)); } catch (e) { return reject(e); } } }); }); }
then方法的工做原理:返回一个新的Promise对象,且向原Promise对象中的handlers添加一个包含回调函数的对象,若是Promise处于Settled状态,那就直接执行回调函数,不然,得等待Promise的状态设置。
private execHandlers = () => { if (this.state === States.PENDING) return; this.handlers.forEach(handler => { if (this.state === States.REJECTED) { return handler.onFail(this.value); } return handler.onSuccess(this.value); }); this.handlers = []; }; private attachHandler = (handler: any) => { this.handlers.push(handler); this.execHandlers(); };
按照原生的Promise.then()方法的逻辑来说,原Promise的状态会直接影响到then
方法返回的Promise的状态,所以设置状态的resolve
和reject
函数逻辑以下:
private resolve = value => this.setResult(value, States.RESOLVED); private reject = value => this.setResult(value, States.REJECTED); private setResult = (value, state: States) => { const set = () => { if (this.state !== States.PENDING) return; this.value = value; this.state = state; return this.execHandlers(); }; setTimeout(set, 0); };
由于没法实现真正的Promise的微任务,所以只可以经过setTimeout(fn,0),勉强来模拟实现
private resolve = value => this.setResult(value, States.RESOLVED); private reject = value => this.setResult(value, States.REJECTED); private setResult = (value, state: States) => { const set = () => { if (this.state !== States.PENDING) return; this.value = value; this.state = state; return this.execHandlers(); }; setTimeout(set, 0); };
离真正的微任务在一些特别的代码上仍是有很大差距的,由于setTimeout是宏任务,在execHandlers
方法中经过foreach 执行本次Promise的handlers中的回调函数时,处于同一个事件循环,如下的代码就会和真正的Promise有出入。
function test(res, rej) { console.log('executor'); setTimeout(() => { console.log('Timer'); res(1); }, 1000); } console.log('start'); let promise1 = new MyPromise(test); // <== 这里替换成原生的Promise,会发现promise2状态不一样 let promise2 = promise1.then(res => { console.log(res); return 2; }); let promise3 = promise1.then(res => { console.log(promise2); //原生的状态是resolve,MyPromise的状态是pending }); console.log('end');
就拿上面的demo来理解整个Promise帮助咱们作了什么吧!
then
方法 => 建立一个新的promise实例赋于给promise2,而且新的promise实例的executor
执行promise1的attachHandler
,将then函数中的回调函数对象push进promise1的handlers
属性中,若是promise1已是settled状态,直接更加promise1的状态来执行不一样函数handlers
数组中有两个持有回调函数的对象,这两个Promise3和promise2都等着promise1的setResult
来执行相应的回调,所以promise3和promise此时属于pending状态res/rej
状态执行handlers中的全部then添加的回调,Promise类的catch
和finally
都是在then上创建的语法糖,具体你们能够更具MDN的定义来实现,还有Promise类的静态方法,能够参考我本身GitHub的实现。
不断的沉淀下来,总归会理解一个东西存在的意义。理解了promise的原理以后,去理解其余的底层实现有会一个很好的基础,了解了Promise底层以后,深深的感觉到设计模式的强大。
若是小伙伴们以为不错的话,点赞支持一下嗷 铁汁~
如下是用Typescript实现的MyPromise源代码,不过参数并无用类型,因此称做es6的class语法也不为过。
enum States { PENDING = 'PENDING', RESOLVED = 'RESOLVED', REJECTED = 'REJECTED' } export class MyPromise { private state: States = States.PENDING; private handlers: any[] = []; private value: any; constructor(callback: (resolve, reject) => void) { try { callback(this.resolve, this.reject); } catch (e) { this.reject(e); } } private resolve = value => this.setResult(value, States.RESOLVED); private reject = value => this.setResult(value, States.REJECTED); private setResult = (value, state: States) => { const set = () => { if (this.state !== States.PENDING) return; this.value = value; this.state = state; return this.execHandlers(); }; setTimeout(set, 0); }; private execHandlers = () => { if (this.state === States.PENDING) return; this.handlers.forEach(handler => { if (this.state === States.REJECTED) { return handler.onFail(this.value); } return handler.onSuccess(this.value); }); this.handlers = []; }; private attachHandler = (handler: any) => { this.handlers.push(handler); this.execHandlers(); }; then(onSuccess?, onFail?) { return new MyPromise((resolve, reject) => { return this.attachHandler({ onSuccess: result => { if (!onSuccess) return resolve(result); try { return resolve(onSuccess(result)); } catch (e) { return reject(e); } }, onFail: reason => { if (!onFail) return reject(reason); try { return resolve(onFail(reason)); } catch (e) { return reject(e); } } }); }); } }
参考连接
https://www.freecodecamp.org/news/how-to-implement-promises-in-javascript-1ce2680a7f51/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise