js中的異步,剛開始的時候都是用回調函數實現的,因此若是異步嵌套的話,就有出現回調地獄,使得代碼難以閱讀和難以維護,後來es6出現了promise,解決了回調地獄的問題。如今咱們就本身寫代碼實現一下promise,這樣才能深刻理解promise的運行機制,對之後使用promise也可以更加駕輕就熟。開始以前能夠先看下promise的官網
promise/A+javascript
先來看下promise的用法java
new Promise((resolve,reject)=>{ resolve(1); reject(11); }).then(res=>{ console.log(res); setTimeout(()=>{ return new Promise((resolve,reject)=>{ resolve(2) }) },1000) }).then(res2=>{ console.log(res2); });
控制檯打印
1
...1s later
2es6
先分析下上面這段代碼,先提出幾個問題
1.第一段resolve和reject都有,可是隻輸出了1,爲何?
2.then裏的res是如何取到resolve中的值的?
3.promise是如何作到鏈式調用的?數組
promise中有個狀態機的概念,先說下爲何要有狀態機的概念呢,由於promise的狀態是單向變化的,有三種狀態,pending,fullfilled,rejected,而這三種狀態只能從pending->fullfilled或者pending->rejected這兩種形式,也就是說執行了fullfilled以後,就不會執行rejected。這就解釋了上面的第一個問題。promise
下面咱們來看下具體實現的完整代碼異步
const PENDING = 'PENDING'; const FULLFILLED = 'FULLFILLED'; const REJECTED = 'REJECTED'; class Promise{ constructor(fn){ this.status = PENDING;//狀態 this.data = undefined;//返回值 this.defercb = [];//回調函數數組 //執行promise的參數函數,並把resolve和reject的this綁定到promise的this fn(this.resolve.bind(this),this.reject.bind(this)); } resolve(value){ if(this.status === PENDING){ //只能pending=>fullfied this.status = FULLFILLED; this.data = value; this.defercb.map(item=>item.onFullFilled()); } } reject(value){ if(this.status === PENDING){ //只能pending=>rejected this.status = REJECTED; this.data = value; this.defercb.map(item=>item.onRejected()); } } then(resolveThen,rejectThen){ //若是沒有resolveThen方法,保證值能夠穿透到下一個then裏有resolveThen的方法中 resolveThen = typeof resolveThen === 'function' ? resolveThen : function(v) {return v}; rejectThen = typeof rejectThen === 'function' ? rejectThen : function(r) {return r}; //返回的都是promise對象,這樣就能夠保證鏈式調用了 switch(this.status){ case PENDING: return new Promise((resolve,reject)=>{ const onFullFilled = () => { const result = resolveThen(this.data);//這裏調用外部then的resolveThen方法,將值傳回去 //若是返回值是promise對象,執行then方法,取它的結果做爲新的promise實例的結果,由於this.data會從新賦值 result instanceof Promise && result.then(resolve,reject); } const onRejected = ()=>{ const result = rejectThen(this.data); result instanceof Promise && result.then(resolve,reject); } this.defercb.push({onFullFilled,onRejected}); }); break; case FULLFILLED: return new Promise((resolve,reject)=>{ const result = resolveThen(this.data); result instanceof Promise && result.then(resolve,reject); resolve(result); }) break; case REJECTED: return new Promise((resolve,reject)=>{ const result = rejectThen(this.data); result instanceof Promise && result.then(resolve,reject); reject(result) }) break; } } }
運行下面的例子函數
new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }).then((res2) => { console.log(res2); return new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000); }); }).then((res3) => { console.log(res3); return new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 1000); }); }).then((res4) => { console.log(res4); });
控制檯打印
...1s later
1
...1s later
2
...1s later
3
說明上面的實現是沒有問題的
不過還有一個問題,就是事件循環的順序問題,好比執行下面的代碼this
new Promise((resolve) => { resolve(); }) .then(() => { console.log('1'); }) .then(() => { console.log('2'); }); console.log('3');
並無像預想中輸出3,1,2,而是輸出了1,2,3,緣由就是由於咱們的這個Promise是在主線程中,沒有在下一個任務隊列中,能夠加上settimeout解決這個問題,不過這也只是爲了讓咱們更好理解執行順序而已,然而其實是promise是屬於微任務中的,而settimeout是屬於宏任務,仍是不太同樣的線程