重學(xué)JS(三)—— promise

Promise這東西,只會用,沒有刻意去了解過。但有時不得不為它帶來的便利感到驚嘆。用的多了,對他的思想就會有一點了解,越來越覺得和消息訂閱模式有異曲同工之妙。

為什么要有promise呢。以讀文件為例:

var reader = new FileReader();
reader.readAsText(file);
reader.onload = function(f){
}

如果想要操作文件內(nèi)容,就必須得在onload中進(jìn)行操作。很麻煩,項目中不可能永遠(yuǎn)寫這幾行代碼,所以封裝下

function readFile(file,fnc){
    var  reader = new FileReader();
    reader.readAsText(file);
    reader.onload = function(f){
        fnc();
    }
}

通過傳入fnc函數(shù),讓FileReader對象在onload中調(diào)用。這種解決異步的方式叫回調(diào)。
好像promise目前沒有出場的必要。
直到有一天,老板讓你連續(xù)讀文件,前一個文件讀完才允許讀下一個,讀4個。

readFile(a,()=>{
    readFile(b,()=>{
        readFile(c,()=>{
              readFile(d,()=>{
              })  
        })
    })
})

第一天寫完可能還好,記得調(diào)用順序,等過幾天再看到這坨估計就要抓狂了。真實代碼中更是會夾雜許多邏輯,加上幾個if else帶來的花括號,想要快速理清這個嵌套關(guān)系,說出執(zhí)行順序幾乎是不可能的。
所以,想要編寫可維護(hù)代碼,一種更順序的寫法來解決異步顯得十分重要。
最理想的方式當(dāng)然是這樣

readFile(a,funcA);
readFile(b,funcB);
readFile(c,funcC);
readFile(d,funcD);

但是他不能保證讀完A文件后才讀B。
突然,你靈機一動,想到了消息訂閱模式。不去直接執(zhí)行讀取文件的函數(shù),而是依次添加訂閱。這樣,順序性也就解決了。試試!

class Mypromise{
    constructor(fn){
          this._topics= [];  //管理訂閱的事件
          fn(this._resolve.bind(this));  //_resolve函數(shù)中用到了this,所以這里綁定下,保證能夠找到_topics對象
    }
    then(callback){  //添加訂閱
          this._topics.push(callback);
          return this;   //為了持續(xù)添加訂閱
    }
     _resolve(val){  //發(fā)布消息
          this._topics.forEach(callback=>{
               callback(val);   
          })
    }
}

使用then函數(shù)來添加訂閱,resolve函數(shù)發(fā)布消息。寫段測試代碼測試下,這里使用簡單的定時器來模擬異步操作:

function readFile(a){
    return new Mypromise(resolve=>{
          setTimeout(()=>{
              console.log(a);
              resolve(a);
          },500)
    })
}

readFile('a')
.then(()=>{
   console.log('b')
})
.then(()=>{
    console.log('c')
});

按順序打出了a b c。效果已經(jīng)出來了。這樣寫我們可以馬上看清誰先調(diào)用,誰后調(diào)用。并且解決了異步問題。
有人就有疑問了,如果readFile函數(shù)里全是同步代碼,你還沒有通過.then添加回調(diào)函數(shù),你就resolve了,那不就什么函數(shù)都沒有被調(diào)用。
記得setTimeout(function(){},0)這個經(jīng)典面試題嗎,它會將在0S后將函數(shù)推入事件隊列,當(dāng)前同步代碼執(zhí)行完后,才會開始執(zhí)行。所以只要用它把_resolve函數(shù)內(nèi)部實現(xiàn)包裹一下,就能解決這個問題。

 _resolve(val){  //發(fā)布消息
    setTimeout(()=>{
        this._topics.forEach(callback=>{
             callback(val);   
        })
    },0)
}

這樣就能保證所有的.then都執(zhí)行完再resolve了。
Promise最好用的一點是每個then返回的都是一個新的Promise對象,而不是原來的promise實例,如下:

let p = Promise.resolve('1');
p.then(json=>{
    console.log(1);   //1
    return 2  
}).then(json=>{
    console.log(json)   //2
});

第二個會輸出2是因為第一個then返回了一個新的Promise對象。第二個then的回調(diào)加在了這個新的Promise對象中。所以我們的then函數(shù)不能return this,而是要return Mypromise。

then(callback){
    this._topics.push(callback);
    return new Mypromise(resolve=>{
        resolve(callback());
    })
}

但是這樣每次.then的時候立刻執(zhí)行了callback,顯然不符合要求。且沒有達(dá)成傳遞的要求。所以銜接前一個promise和后一個promise變得至關(guān)重要。
其實也簡單,調(diào)用then函數(shù)往_topics塞回調(diào)的時候不僅把callback塞進(jìn)去,也把新生成的promise對象的resolve也塞進(jìn)去。執(zhí)行resolve的時候不僅要執(zhí)行callback,也要執(zhí)行resolve,即觸發(fā)下一個then的回調(diào),修改后完整代碼如下:

class Mypromise{
    constructor(fn){
        this._topics= [];  //管理訂閱的事件
        fn(this._resolve.bind(this));
    }
    then(callback){  //添加訂閱
        return new Mypromise(resolve=>{
          this._topics.push({
              callback:callback,   //當(dāng)前then添加的回調(diào)函數(shù)
              resolve:resolve   //then新生成promise對象的resolve,用于觸發(fā)該promise回調(diào)
          })
        });
     }
     _resolve(val){  //發(fā)布消息
        setTimeout(()=>{
            this._topics.forEach(call=>{
                var result = call.callback(val);   //執(zhí)行當(dāng)前promise注冊的回調(diào)
                call.resolve(result);   //觸發(fā)新生成promise的回調(diào)
            })
        },0)
     }
}
function readFile(a){
    return new Mypromise(resolve=>{
        setTimeout(()=>{
            console.log(a);
            resolve(1);
        },500)
    })
}

readFile('a')
.then(json=>{
   console.log(json);
   return 2;
})
.then(json=>{
   console.log(json)
});

打印出了理想的a,1,2。一個極簡的promise就完成了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容