小邵教你玩轉promise源碼

前言:你們好,我叫邵威儒,你們都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程序猿的坑,從大學買的第一本vb和自學vb,我就與編程結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟件,至今進入了前端領域,看到很多朋友都寫文章分享,本身也弄一個玩玩,如下文章純屬我的理解,便於記錄學習,確定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享我的對技術的通俗理解,共同成長!javascript

後續我會陸陸續續更新javascript方面,儘可能把javascript這個學習路徑體系都寫一下
包括前端所經常使用的es六、angular、react、vue、nodejs、koa、express、公衆號等等
都會從淺到深,從入門開始逐步寫,但願能讓你們有所收穫,也但願你們關注我~前端

源碼地址:github.com/iamswr/prom…
文章列表:juejin.cn/user/372040…vue

Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/java


JavaScript做爲單線程語言,其特色也是其缺陷,特色就是不用處理多線程引起的佔用資源、衝突啪啦啪啦等,缺陷就是同一時間,只能作一件事情,那麼會存在一個問題,網絡傳輸是有延遲的,好比A發一條信息到B服務器,在B服務器還沒返回信息給A時,那麼A就會一直在等待接收信息,會形成頁面的假死,那麼該怎麼辦?俗話說得好,程序猿改變世界,因而乎出現了異步的概念,我會分如下幾點,去講述我對前端異步的理解:node

  1. callback
  2. 閉包、高階函數
  3. promise
  4. generator ( 文章地址:juejin.cn/post/684490…
  5. async / await ( 文章地址:juejin.cn/post/684490…

一.callback

回調函數被認爲是一種高級函數,一種被做爲參數傳遞給另外一個函數(在這稱做"otherFunction")的高級函數,回調函數會在otherFunction內被調用(或執行)。回調函數的本質是一種模式(一種解決常見問題的模式),所以回調函數也被稱爲回調模式。react

是否是看起來一頭懵逼,到底什麼是回調函數?有什麼做用?我對回調函數理解,從真正意義上讓我忽然間恍然大悟的,就是當初研究jQuery底層源碼的時候,咱們看如下一段代碼:jquery

<img src='../a.jpg'></img>
<img src='../b.jpg'></img>
<img src='../c.jpg'></img>

$("img").attr("title",function(index,attr){
    console.log(index) // 依次返回0 1 2
});
複製代碼

在attr方法的第二個參數,傳入了一個function,而該函數,會依次獲取$('img')的DOM對象對應index和attr,咱們能夠在該function裏,寫咱們須要的業務邏輯,那麼這樣有什麼好處呢?git

個人理解是,假如我要封裝一個庫,造一個輪子,那麼要考慮到通用性和複用性而且提供一個途徑,讓使用者任意發揮想象寫業務邏輯,而且把相關可能使用到的參數,都傳給使用者。es6

假設,咱們如今有一個需求,要寫一個判斷類型的方法,常見的方法有如下幾種:github

- typeOf // 簡單的數據類型判斷,棧區
 - instanceof // 複雜的數據類型,堆區
 - constructor // 複雜的數據類型,主要是用在繼承的改寫指向的構造函數,不多用於判斷類型
 - Object.prototype.toString.call() // 絕大多數庫底層都是使用該方式,返回值如[object String]
複製代碼

首先,咱們寫一個isType方法

function isType(content,type,fn){
    // 類型判斷
    let t = Object.prototype.toString
                              .call(content)
                              .replace(/\[object\s|\]/g,'')
                              
    // 判斷完成後,執行傳入的callback函數
    fn(type,t)
}
複製代碼

如今咱們要判斷一個值的類型,而後拿到這個類型,最終執行咱們須要作的事情

isType('hello swr','String',function(type,t){ // 做爲參數傳入的函數,接收isType函數內的fn中type和t這兩個參數
    console.log(type === t) // true
})
複製代碼

首先執行了isType函數,而後執行isType函數內部的代碼,isType內的fn(type,t),這裏的fn方法,實際就是咱們傳入的第三個參數,即裏面只有一行console.log(type === t)的函數,而t則是isType函數內的t,能夠稍微把整個流程體會一下,就差很少明白回調函數是幹嗎的了~

那麼問題就出現了,好比咱們使用node.js的時候,進行文件讀取操做時,想獲取的值是一種嵌套依賴關係時,會出現什麼問題呢?

目錄結構:
- iamswr
  - A.txt
  - B.txt
  - C.txt
  
  其中
  A.txt文件裏的內容爲字符串B.txt
  B.txt文件裏的內容爲字符串C.txt
  C.txt文件裏的內容爲字符串'hello swr'
  
  那麼當咱們想獲取到'hello swr',會遇到什麼問題呢?請看下面的代碼
  let fs = require('fs')
  fs.readFile('A.txt','utf8',function(err,data){ // 此時回調函數data值爲'B.txt'
      fs.readFile(data,'utf8',function(err,data){ // 此時回調函數data值爲'C.txt'
          fs.readFile(data,'utf8',function(err,data){
            console.log(data) // 'hello swr'
          })
      })
  })
複製代碼

以上這個例子若是嵌套依賴層次更高一些,那代碼變得十分難維護以及難閱讀,咱們在企業開發當中,常常會遇到想獲得的數據,是經過嵌套依賴的關係,最終纔得到須要的數據,陷入了回調地獄,而es6中,promise解決了這個讓前端頭疼的問題,後面我會詳細講promise,下面咱們先了解一下閉包、高階函數。


二.閉包、高階函數

我我的理解,閉包其實是一種函數,因此閉包技術也是函數技術的一種;閉包能作的事情函數幾乎都能作,閉包有最大的兩個用處,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中。

1.封閉做用域

在javascript中,若是一個對象不被引用了,那麼這個對象會被GC回收,不然則一直保留在內存中,那麼利用這個特色,配合閉包使用,有如下幾個優勢:封閉做用域、保存做用域、做用域鏈條。

不污染全局變量,當團隊協做時,好比A大佬,封裝了jQuery庫,而jQuery庫內是有大量變量,若是不使用閉包,則jQuery庫內的變量會污染整個項目,甚至和其餘團員的變量有衝突

外部沒法獲取閉包內的變量,封閉了做用域
(function(){
    var str = 'hello swr'
    console.log(str) // 'hello swr'
})()

console.log(str) // 報錯

咱們用原生js來寫代碼的時候,會存在一個問題,
好比有5個button標籤
var btns = document.getElementsByTagName('button');
for(var i=0; i< btns.length; i++){   
   var btn = btns[i];        
   btn.onclick = function () {            
     alert('點擊了第' + i + '個按鈕');       
   }
}
不管咱們點擊哪一個button,都是彈出'點擊了第5個按鈕',
由於btn.onclick事件是異步觸發的,當事件被觸發時,
for循環早已經結束,此時變量I的值已是5,
全部onclick事件函數從內到外查找變量i時,查找到的值老是5。

能夠經過封閉做用域把每次循環的i值都封閉起來,
當時間函數順着做用域鏈從內到外查找變量i時,
會先找到被封閉在閉包環境中的i,
若是有5個按鈕, 則i的值就是0,1,2,3,4
var btns = document.getElementsByTagName('button');
for(var i=0; i< btns.length; i++){   
    (function (i) {        
       var btn = btns[i];        
       btn.onclick = function () {            
         alert('點擊了第' + i + '個按鈕');       
       }    
     })(i);
}

複製代碼

2.做用域鏈

咱們知道,在es6以前,只有函數是有做用域的說法,在es6出現了,則有了塊級做用域的說法,好比

(function person(){
    var name = '邵威儒'
    console.log(name) // '邵威儒'
})()

console.log(name) // 報錯
複製代碼

在函數外部,是訪問不了內部的name,這就是做用域。 在es6出了一個新的概念,就是塊級做用域

{
    let name = '邵威儒'
    console.log(name) // '邵威儒'
}

console.log(name) // 報錯
複製代碼

效果和閉包同樣

3.保存做用域

函數嵌套函數,那麼內部的那個函數將造成做用域閉包。簡單的說,這種閉包可以達到的好處就是讓指令可以綁定一些全局數據去運行,優勢是全局數據隱藏化、 將數據綁定在指令上運行,讓指令再也不依賴全局數據。

function plus(num){
    ++num
    return function(){
        console.log(num)
    }
}

let toPlus = plus(5)
此時toPlus實際上爲
function(){
    console.log(num)
}
而這個num實際上就是plus函數內做用域的num,此時咱們沒法從外部修改num,並且把plus函數內的數據隱藏化,將數據綁定在toPlus上運行。
複製代碼

實際開發中遇到的問題

好比說,咱們實際開發中會遇到一個問題,就是某個函數,要等多個異步執行完畢後才執行,這種狀況怎麼作呢?

通常會想到如下這個辦法

let fs = require('fs')
let arr = []

fs.readFile('./a.txt','utf8',function(err,data){
    arr.push(data) // 假設data爲'hello'
})

fs.readFile('./b.txt','utf8',function(err,data){
    arr.push(data) // 假設data爲'swr'
})

console.log(arr) // 咱們但願打印出來是['hello','swr']或['swr','hello'],可是打印出來的倒是[]
這是爲何呢?
是由於javascript執行原理,是先執行同步,再執行異步的,而fs.readFile方法屬於異步方法,因此還沒執行完畢,就已經執行了console.log(arr)了
複製代碼

對於這種並不是依賴嵌套獲取,咱們稱爲「同步」獲取,此同步非異步同步的那個同步,特別是這種異步請求的數據,獲取到的時間前後順序不一樣,那咱們該如何實現「同步」獲取呢?

let fs = require('fs')

function after(times,callback){
    let arr = []
    return function(data){
        arr.push(data)
        if(--times === 0){
            callback(arr)
        }
    }
}

let fn = after(2,function(arr){
    console.log(arr) // 當fn執行兩次後,則會執行該回調函數
})

fs.readFile('./a.txt','utf8',function(err,data){
    fn(data) // 假設data爲'hello'
})

fs.readFile('./b.txt','uft8',function(err,data)=>{
    fn(data) // 假設data爲'swr'
})

最終當2個fs.readFile讀取完畢後,執行了fn()達到2次時,則會打印出['hello','swr']或者['swr','hello']
複製代碼

雖然以上的方式,實現了咱們須要的需求,可是問題來了,難道咱們每一次都要特地寫一個after函數嗎?其實還有一個概念,叫作發佈訂閱,訂閱就相似你收藏了這個電臺,而發佈,則是這個電臺向全部收藏了本電臺的粉絲進行廣播,看下面代碼

let fs = require('fs')

let event = {
    arr:[], // 存須要執行的函數
    result:[], // 存結果
    on(fn){ // 訂閱
        this.arr.push(fn)
    },
    emit(data){ // 發佈
        this.result.push(data)
        this.arr.forEach(fn=>fn(this.result))
    }
}

event.on(function(data){
    if(data.length === 2){
        console.log(data) // ['hello','swr'] 或者 ['swr','hello']
    }
})

fs.readFile('./a.txt','utf8',(err,data)=>{
    event.emit(data) // data爲'hello'
})

fs.readFile('./b.txt','utf8',(err,data)=>{
    event.emit(data) // data爲'swr'
})

當兩個fs.readFile讀取完成,而且在其回調函數內執行了event.emit,最終會打印出['hello','swr'] 或者 ['swr','hello']
複製代碼

三.Promise

囉囉嗦嗦說了那麼多,主要是想你們瞭解一下回調函數以及閉包,由於這概念和promise的緊密關聯的,promise部分我主要是想和你們根據promiseAplus規範,逐步手寫一個promise的底層實現方式。

首先,promise怎麼理解?我在知乎上看到一篇比較通俗易懂的小故事,你們能夠看看,zhuanlan.zhihu.com/p/19622332

早上,老爸說:「兒子,天氣如何?」 每週一早上,老爸問兒子下午的天氣狀況,兒子能夠到自家房子旁邊小山上使用望遠鏡來觀看。兒子在出發時許諾(Promise)老爸(會通知老爸天氣狀況)。 此刻,老爸決定,若是天氣不錯,明天就出去捕魚,不然就不去。並且若是兒子沒法得到天氣預報的話,也不去捕魚。 30分鐘左右,兒子回來了,每週的結局都不同。

結局A:成功得到了(retrieved)天氣預報,晴天 :) 兒子成功獲取了天氣預報,天空晴朗,陽光明媚!承諾(Promise)兌現了(resolved),因而老爸決定開始爲週日的捕魚作準備。

結局B:一樣成功得到了天氣預報,雨天:( 兒子成功得到了天氣預報,只不過是烏雲密佈,要下雨。承諾(Promise)兌現了(resolved),只是老爸決定呆在家裏,由於天氣很糟糕。

結局C:無法得到天氣預報:-/ 出了問題,兒子無法得知天氣預報,由於霧很大,就算站在小山上也沒法看清。兒子沒辦法對象他離開時許下的諾言, promise was rejected!老爸決定留下來,這並不值得冒險。

Promise的一些特性

首先咱們要了解PromiseA+規範 promisesaplus.com/

  • promise是有兼容性問題的,node環境下默認支持,還能夠下載相應插件來解決兼容性問題
  • promise是有三種狀態的,等待態pending / 成功態resolved / 失敗態rejected
  • promise的狀態是能夠轉換的,能夠從pending -> resolved 或 pending -> rejected,可是resolved不能轉換爲rejected/pending,rejected不能轉換爲resolved/pending,簡而言之即狀態只會更改一次
// Promise構造函數的第一個參數爲executor
let promise = new Promise(function(resolve,reject){
    console.log('我是會被當即執行的喲')
})

// promise的實例都有then方法
promise.then(()=>{ // 成功的回調
    
},()=>{ // 失敗的回調
    
})
複製代碼
  • executor默認在new的時候會自動執行
  • 每一個promise的實例都有then方法
  • then方法中,有兩個參數,分別是成功的回調函數和失敗的回調函數
// 默認時爲pending態,既不會走成功的回調也不會走失敗的回調
promise.then(()=>{
    console.log('success1')
},()=>{
    console.log('error1')
})

console.log('2')

在這段代碼中,只會打印出'2',由於promise一直處於pending態,不會走then後的回調函數
複製代碼
let promise = new Promise(function(resolve,reject){
    console.log('1')
    resolve() // 更改pending狀態爲resolved
})

promise.then(()=>{
    console.log('success1')
},()=>{
    console.log('error1')
})

console.log('2')

此時輸出順序爲'1' -> '2' -> 'success1'
複製代碼
  • then方法是異步的,屬於微任務,從上面的例子能夠看出,先執行完同步代碼,再執行異步代碼
let promise = new Promise(function(resolve,reject){
    console.log('1')
    setTimeout(()=>{ // 異步行爲
        resolve() // 更改狀態爲成功
    },1000)
})

promise.then(()=>{
    console.log("success1")
})

promise.then(()=>{
    console.log('success2')
})

console.log("2")

此時輸出順序爲'1' -> '2' -> 'success1' -> 'success2'
複製代碼
  • 同一個promise的實例能夠then屢次,成功時會調用全部的成功方法,失敗時會調用全部的失敗方法
  • new Promise中能夠支持異步行爲
let promise = new Promise(function(resolve,reject){
    throw new Error('出錯了') // 拋出錯誤
})

promise.then(()=>{
    console.log('success1')
},()=>{
    console.log('error1')
})

此時輸出爲 'error1'
複製代碼
  • 若是發現錯誤,就會進入失敗態

實現一個Promise

下面代碼部分和源碼實現部分要結合來看

// ----- 代碼部分
// 1.executor默認在new的時候會自動執行
// 成功和失敗的視乎能夠傳遞參數
let promise = new Promise((resolve,reject)=>{ // 6.resolve、reject函數對應源碼實現部分的resolve、reject函數
    resolve('hello swr') // 11.執行resolve
})

// 7.Promise的實例都有then方法
promise.then((data)=>{ // 8.成功的回調函數
    
},(err)=>{ // 9.失敗的回調函數
    
})
複製代碼
// ----- 源碼實現部分
// 2.聲明一個Promise構造函數
function Promise(executor){
    let self = this
    self.value = undefined
    self.reason = undefined // 12.由於value和reason值須要在Promise實例方法then中使用,因此把這兩個值,賦給new出來的實例
    function resolve(value){ // 3.聲明一個resolve函數
        self.value = value // 13.當調用了resolve而且傳參數時,則把這value值賦予self.value
    }
    
    function reject(reason){ // 4.聲明一個reject函數
        self.reason = reason // 13.當調用了reject而且傳參數時,則把這reason值賦予self.reason
    }
    executor(resolve,reject) // 5.把resolve、reject函數傳到executor
}

// 由於Promise的實例都有then方法,那麼意味着then方法是在Promise的原型對象中的方法
// 10.對應上面成功的回調函數onFulfilled以及失敗的回調函數onRejected
Promise.prototype.then = function(onFulfilled,onRejected){
    
}

module.exports = Promise // 把Promise暴露出去
複製代碼

此時,咱們會發現,如何去判斷調用resolve仍是reject呢? 這個時候咱們在內部應該維護一個狀態,而咱們以前說過了Promise有三種狀態,分別爲pending、resolved、rejected,那麼咱們接着看下面的代碼。

// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
    resolve('hello swr') // 5.暫時忽略此行
    resolve('看看同時執行resolve和reject會發生什麼?')  // 5.此行執行resovle
    reject('看看同時執行resolve和reject會發生什麼?') // 5.此行執行reject
})

promise.then((data)=>{
    console.log('success:' + data) // 5.當調用了resolve函數,則輸出success:hello swr
},(err)=>{
    
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
    let self = this
    self.value = undefined
    self.reason = undefined 
    self.status = 'pending' // 1.在內部維護一個status狀態
    function resolve(value){
        self.value = value 
        self.status = 'resolved' // 2.當調用了resolve時,更改狀態爲resolved
    }
    
    function reject(reason){
        self.reason = reason 
        self.status = 'rejected' // 2.當調用了reject時,更改狀態爲rejected
    }
    executor(resolve,reject)
}

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this
    // 3.當咱們在then中,執行了成功或者失敗的回調函數時,首先要判斷目前處於什麼狀態
    if(self.status === 'resolved'){
        onFulfilled(self.value) // 4.當調用了resolve函數後,會執行成功的回調函數,而且把resolve中傳遞的值,傳遞給成功的回調函數
    }
    
    if(self.status === 'rejected'){
        onRejected(self.reason) // 4.當調用了reject函數後,會執行成功的回調函數,而且把reject中傳遞的值,傳遞給失敗的回調函數
    }
}

module.exports = Promise
複製代碼

當咱們在上面5中同時執行resolve和reject,會發現都可以執行,那麼就違背了狀態只能更改一次的原則了,下面咱們來解決這個問題。

// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
    resolve('看看同時執行resolve和reject會發生什麼?') // 1. 此時執行resolve和reject
    reject('看看同時執行resolve和reject會發生什麼?') // 3.此時即便調用reject,由於resolve已經調用了一次,從pending更改成resolve,因此在第一次調用後,屢次調用也不會生效
    
    // 4.以上resolve、reject暫時忽略掉,咱們考慮一個狀況,當promise拋出錯誤時,怎麼去處理呢?
    throw new Error('出錯啦')
})

promise.then((data)=>{
    console.log('success:' + data)
},(err)=>{
    
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
    let self = this
    self.value = undefined
    self.reason = undefined 
    self.status = 'pending'
    function resolve(value){
        if(self.status === 'pending'){ // 2.此時新增一個狀態判斷,當狀態爲pending的時候才能執行
            self.value = value 
            self.status = 'resolved'
        }
    }
    
    function reject(reason){
        if(self.status === 'pending'){ // 2.此時新增一個狀態判斷,當狀態爲pending的時候才能執行
            self.reason = reason 
            self.status = 'rejected'
        }
    }
    
    // 5.當咱們在執行executor時,內部拋出錯誤的時候,能夠利用try catch來處理這個問題
    try{
        executor(resolve,reject)
    }catch(error){
        reject(error)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this
    if(self.status === 'resolved'){
        onFulfilled(self.value) 
    }
    
    if(self.status === 'rejected'){
        onRejected(self.reason) 
    }
}

module.exports = Promise
複製代碼

這樣咱們就解決了屢次調用,只認第一次的更改狀態,而且當拋出錯誤時,使用try catch來處理,那麼接下來,咱們想一下,目前咱們都是new一個Promise,而後調用then,這整個流程,彷彿沒任何問題,可是,如今問題出現了,若是此時resolve或者reject是處於setTimeout(()=>{resolve()},3000)中,即處於異步中,當咱們new一個Promise時,不會立刻執行異步代碼,而是直接執行了promise.then這個函數,而此時由於self.status的狀態依然是處於pending,因此不會執行resolve或者reject,當同步代碼執行完畢後,執行異步代碼時,更改了狀態爲resolved或者rejected時,此時then方法已經執行完畢了,不會再次執行then的方法,那麼此時咱們該如何處理?

還存在一個問題,就是上面所說的,同一個promise的實例能夠then屢次,成功時會調用全部的成功方法,失敗時會調用全部的失敗方法,那這個又該如何處理呢?

能夠利用咱們前面所說的發佈訂閱的思路來解決,如今咱們看下面代碼。

// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
    setTimeout(()=>{ // 1.此時resolve處於異步
        resolve('hello swr')
    },3000)
})

promise.then((data)=>{ // 多個then
    console.log('success1:' + data)
},(err)=>{
    
})
promise.then((data)=>{ // 多個then
    console.log('success2:' + data)
},(err)=>{
    
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
    let self = this
    self.value = undefined
    self.reason = undefined 
    self.status = 'pending'
    self.onResolvedCallbacks = [] // 2.可能new Promise中會有異步的操做,此時咱們把異步操做時,執行的then函數的成功回調,統一保存在該數組中
    self.onRejectedCallbacks = [] // 2.可能new Promise中會有異步的操做,此時咱們把異步操做時,執行的then函數的失敗回調,統一保存在該數組中
    function resolve(value){
        if(self.status === 'pending'){ 
            self.value = value 
            self.status = 'resolved'
            // 4.當調用resolve時,把該數組中存放的成功回調都執行一遍,若是是異步,則會把成功的回調都存到該數組裏了,若是是異步,則沒存到。
            self.onResolvedCallbacks.forEach(fn=>fn())
        }
    }
    
    function reject(reason){
        if(self.status === 'pending'){ 
            self.reason = reason 
            self.status = 'rejected'
            // 4.當調用reject時,把該數組中存放的失敗回調都執行一遍,若是是異步,則會把成功的回調都存到該數組裏了,若是是異步,則沒存到。
            self.onRejectedCallbacks.forEach(fn=>fn())
        }
    }
    
    try{
        executor(resolve,reject)
    }catch(error){
        reject(error)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this
    if(self.status === 'resolved'){
        onFulfilled(self.value) 
    }
    
    if(self.status === 'rejected'){
        onRejected(self.reason) 
    }
    
    // 3.當new Promise中有resolve、reject處於異步中,執行then的時候,狀態爲pending,
    if(self.status === 'pending'){
        self.onResolvedCallbacks.push(()=>{
            onFulfilled(self.value)
        }) // 3. 把成功的回調函數,存到該數組中,這樣寫的好處,就是把參數傳進去,不須要未來遍歷onResolvedCallbacks時,再傳參
        self.onRejectedCallbacks.push(()=>{
            onRejected(self.reason)
        }) // 3. 把失敗的回調函數,存到該數組中,這樣寫的好處,就是把參數傳進去,不須要未來遍歷onRejectedCallbacks時,再傳參
    }
}

module.exports = Promise
複製代碼

到此爲止,咱們簡版的Promise實現得差很少了,小夥伴們能夠對着代碼敲一下,感覺一下,體會一下。


Promise的鏈式調用

其實Promise的核心在於鏈式調用,Promise主要是解決2個問題:

  • 回調地獄
  • 併發異步io操做,同一時間內把這個結果拿到,即好比有兩個異步io操做,當這2個獲取完畢後,才執行相應的代碼,好比前面所說的after函數,發佈訂閱、Promise.all等。

首先,好比回調地獄怎麼解決呢?那麼咱們來看下面的代碼,而且改成promise。

// 回調函數
let fs = require('fs')
fs.readFile('./a.txt','utf8',(err,data)=>{ // 往fs.readFile方法傳遞了第三個爲函數的參數
    if(err){
        console.log(err)
        return
    }
    console.log(data)
})

// 改寫爲Promise
let fs = require('fs')
function read(filePath,encoding){
    return new Promise((resolve,reject)=>{
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err)
            resolve()
        })
    })
}

read('./a.txt','utf8').then((data)=>{ // 在這裏則再也不須要傳回調函數進去,而是採用then來達到鏈式調用
    console.log(data)
},(err)=>{
    console.log(err)
})

// 這樣看好像Promise也沒什麼優點,那麼接下來咱們對比一下
// 假設有3個文件
// - 1.txt    文本內容爲'2.txt'
// - 2.txt    文本內容爲'3.txt'
// - 3.txt    文本內容爲'hello swr'

// 用回調函數
fs.readFile('./1.txt','utf8',(err,data)=>{
    fs.readFile(data,'utf8',(err,data)=>{
        fs.readFile(data,'utf8',(err,data)=>{
            console.log(data) // hello swr
        })
    })
})

// 用Promise
read('./1.txt','utf8')
.then((data)=>{
    // 1.若是一個promise執行完後,返回的仍是一個promise,
    //   會把這個promise的執行結果會傳遞給下一次thenreturn read(data,'utf8')
})
.then((data)=>{
    return read(data,'utf8')
})
.then((data)=>{
    // 2.若是在then中返回的不是一個promise,
    //   而是一個普通值,會將這個普通值做爲下次then的成功的結果
    return data.split('').reverse().join('')
})
.then((data)=>{
    console.log(data) // rws olleh
    // 3.若是當前then中失敗了,會走下一個then的失敗回調
    throw new Error('出錯')
})
.then(null,(err)=>{
    console.log(err) // Error:出錯   報錯了
    // 4.若是在then中不返回值,雖然沒有顯式返回,
    //   可是默認是返回undefined,是屬於普通值,依然會把這個普通值傳到
    //   下一個then的成功回調中
})
.then((data)=>{
    console.log(data) // undefined
})
複製代碼

從上面能夠看得出,改寫爲Promise的代碼,更好閱讀和維護,從用Promise方式能夠得出結論:

  • 1.若是一個promise執行完後,返回的仍是一個promise,會把這個promise的執行結果會傳遞給下一次then中
  • 2.若是在then中返回的不是一個promise,而是一個普通值,會將這個普通值做爲下次then的成功的結果
  • 3.若是當前then中失敗了,會走下一個then的失敗回調
  • 4.若是在then中不返回值,雖然沒有顯式返回,可是默認是返回undefined,是屬於普通值,依然會把這個普通值傳到下一個then的成功回調中
// 若是在then中拋出錯誤,會怎樣呢?
// 情景一,會被下一個then中的失敗回調捕獲
read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})
.then(null,(err)=>{
    console.log(err) // Error:出錯了   報錯
})

// 情景二,若是沒有被失敗的回調捕獲,拋出錯誤最終會變成異常
read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})

// 情景三,若是沒有被失敗的回調捕獲,那麼最終會被catch捕獲到
read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})
.then((data)=>{
    
})
.catch((err)=>{
    console.log(err) // Error:出錯了   報錯
})

// 情景四,若是被失敗的回調捕獲了,那麼不會被catch捕獲到
read('./1.txt','utf8')
.then((data)=>{
    throw new Error('出錯了')
})
.then(null,(err)=>{
    console.log(err) // Error:出錯了   報錯
})
.catch((err)=>{
    console.log(err)  // 不會執行到這裏
})
複製代碼
  • 5.catch是錯誤沒有處理的狀況下才會執行
  • 6.then中能夠不寫東西

穿插一個與jquery的鏈式調用區別

jquery的鏈式調用,是經過其內部執行完後return this,返回自身這個對象,達到鏈式調用的目的,那爲何Promise不採用這種方式呢?

咱們能夠看如下代碼,感覺一下。

let promise = new Promise((resolve,reject)=>{
    resolve() // 執行resolve,使狀態從pending變爲resolved
})

let promise2 = promise.then(()=>{
    throw new Error() // 拋出錯誤
    return this // 返回自身
})

// 那麼我在promise2中,調then,那麼它會執行失敗的回調嗎?答案是不會的。
// 由於咱們不可能讓狀態既成功又失敗的
// promise成功了,若是返回this,那不能走向失敗
promise2.then(()=>{
    console.log('來到這裏了') 
},()=>{
    console.log('會來到這裏嗎?')
})

// 此時then中返回自身後,promise2其實就是promise,而咱們想達到
// 的是把當前的then返回後,傳到下一個then中,可是咱們這樣返回this,
// 其實會變得很矛盾,由於狀態已經從pending變爲resolved,不可能又從resolved變成rejected的
// 因此得出結論,返回的必須是一個新的promise,由於promise成功後不能再走失敗
// 只能建立一個新的promise再執行業務邏輯,返回同一個promise的話,就不能既成功又失敗
複製代碼
實現Promise鏈式調用
// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
    resolve()
})

// 2.返回的值爲promise2 爲何這樣規定呢?這是promiseA+規範規定的,咱們要遵循
let promise2 = promise.then((data)=>{
    return x // 1.then中的返回值x多是普通值也多是promise,而且傳給下一個then
}).then((data)=>{
    console.log(data) // x的值
})
複製代碼
// ----- 源碼實現部分
function Promise(executor){
    let self = this
    self.value = undefined
    self.reason = undefined 
    self.status = 'pending'
    self.onResolvedCallbacks = []  
    self.onRejectedCallbacks = []
    function resolve(value){
        if(self.status === 'pending'){ 
            self.value = value 
            self.status = 'resolved'
            self.onResolvedCallbacks.forEach(fn=>fn())
        }
    }
    
    function reject(reason){
        if(self.status === 'pending'){ 
            self.reason = reason 
            self.status = 'rejected'
            self.onRejectedCallbacks.forEach(fn=>fn())
        }
    }
    
    try{
        executor(resolve,reject)
    }catch(error){
        reject(error)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this
    let promise2 // 3.上面講promise鏈式調用時,已經說了返回的是一個新的promise對象,那麼咱們聲明一個新的promise
    
    // 4.那麼咱們new一個新的promise,而且把如下代碼放到promise中
    let promise2 = new Promise((resolve,reject)=>{
        if(self.status === 'resolved'){
            // 7.當執行成功回調的時候,可能會出現異常,那麼就把這個異常做爲promise2的錯誤的結果
            try{
                let x = onFulfilled(self.value) // 6.這裏的x,就是上面then中執行完返回的結果,咱們在這裏聲明一個x用來接收
                // 8.根據promiseA+規範,咱們應該提供一個函數來處理promise2
                //   我我的的理解是,then中無論是成功回調仍是失敗回調,其返回
                //   值,有多是promise,也有多是普通值,也有多是拋出錯誤
                //   那麼咱們就須要一個函數來處理這幾種不一樣的狀況
                //   這個函數咱們聲明爲resolvePromise吧
                resolvePromise(promise2,x,resolve,reject)
                // 9. 這裏的promise2就是當前的promise2,x則是執行then中成功回調後返回的結果,若是是成功則調promise2的resolve,失敗則調reject
            }catch(e){
                reject(e) // 注意:這裏的reject是這個promise2的reject
            }
        }
        
        if(self.status === 'rejected'){
            // 同6-7步
            try{
                let x = onRejected(self.reason) 
                // 同8-9
                resolvePromise(promise2,x,resolve,reject)
            }catch(e){
                reject(e)
            }
        }
    
        if(self.status === 'pending'){
            self.onResolvedCallbacks.push(()=>{
                // 同6-7步
                try{
                    let x =  onFulfilled(self.value)
                    // 同8-9
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e)
                }
            }) 
            self.onRejectedCallbacks.push(()=>{
                // 同6-7步
                try{
                    let x = onRejected(self.reason)
                    // 同8-9
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e)
                }
            })
        }
    })
    return promise2 // 5.在jquery中是return this,可是在promise中,則是返回一個新的promise對象
}

module.exports = Promise
複製代碼

寫一個resolvePromise函數

接下來咱們寫一下resolvePromise這個函數,整個Promise最核心的部分就是在這裏

// ----- 代碼部分
let promise = new Promise((resolve,reject)=>{
    resolve()
})

let promise2 = promise.then((data)=>{
    return x 
}).then((data)=>{
    console.log(data) 
})

// 2.咱們在resolvePromise函數中,在原生狀況下,若是傳參的時候,promise2和x是同一個對象會發生什麼呢?
let promise = new Promise((resolve,reject)=>{
    resolve()
})

let promise2 = promise.then(()=>{
    return promise2 
    // 2.1報錯 UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>
    // 報錯的意思是,陷入了死循環,那怎麼理解呢?
    // promise2的成功或失敗是要取決於promise中then的返回結果,而返回的倒是promi2本身
    // 這樣就陷入死循環了,promise2是依賴於promise的then返回的結果,
    // 而then返回的結果是promise2,而then中的promise2,既不是成功也不是失敗,不能本身等於本身
})

// 7.當取一個對象上的屬性,可能存在報異常的狀況,怎麼理解呢?
// 由於這個方法有可能不是本身寫的,可能別人搞惡做劇亂寫的,看如下代碼。
let obj = {}
// 給obj對象定義一個then方法,當咱們去obj對象中調用then方法時
// 就會執行裏面的get,而get則是拋出異常
Object.defineProperty(obj,'then',{
    get(){
        throw new Error()
    }
})

// 10.爲何要用call呢?解決了什麼問題?看一下如下代碼
首先咱們執行
promise.then(()=>{
    console.log(this) // 此時this是指向該promise的,對象的方法中this是指向這個對象的
})

可是咱們在下面經過let then = promise.then,來判斷是否promise,是否會異常
當咱們執行then時,裏面的this仍是會指向這個promise嗎?答案是不必定的,
由於此時then,若是在全局下執行,指向的可能就是window了,因此爲了讓this的
指向正確,咱們須要經過
then.call(promise),來把then的this指向promise
複製代碼
// ----- 源碼實現部分
function Promise(executor){
    let self = this
    self.value = undefined
    self.reason = undefined 
    self.status = 'pending'
    self.onResolvedCallbacks = []  
    self.onRejectedCallbacks = []
    function resolve(value){
        if(self.status === 'pending'){ 
            self.value = value 
            self.status = 'resolved'
            self.onResolvedCallbacks.forEach(fn=>fn())
        }
    }
    
    function reject(reason){
        if(self.status === 'pending'){ 
            self.reason = reason 
            self.status = 'rejected'
            self.onRejectedCallbacks.forEach(fn=>fn())
        }
    }
    
    try{
        executor(resolve,reject)
    }catch(error){
        reject(error)
    }
}

// 1.聲明一個resolvePromise函數
// 這個函數很是核心,全部的promise都遵循這個規範,全部的promise能夠通用,
/**
 * 
 * @param {*} promise2 then的返回值,返回新的promise
 * @param {*} x then中成功函數或者失敗函數的返回值
 * @param {*} resolve promise2的resolve
 * @param {*} reject promise2的reject
 */
function resolvePromise(promise2,x,resolve,reject){
    // 3.從2中咱們能夠得出,本身不能等於本身
    // 當promise2和x是同一個對象的時候,則走reject
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle detected for promise'))
    }
    // 4.由於then中的返回值能夠爲promise,當x爲對象或者函數,纔有可能返回的是promise
    let called
    if(x !== null && (typeof x === 'object' || typeof x === 'function')){
        // 8.從第7步,能夠看出爲何會存在拋出異常的可能,因此使用try catch處理
        try{
            // 6.由於當x爲promise的話,是存在then方法的
            // 可是咱們取一個對象上的屬性,也有可能出現異常,咱們能夠看一下第7步
            let then = x.then 
            
            // 9.咱們爲何在這裏用call呢?解決了什麼問題呢?能夠看上面的第10步
            // x可能仍是個promise,那麼就讓這個promise執行
            // 可是仍是存在一個惡做劇的狀況,就是{then:{}}
            // 此時須要新增一個判斷then是否函數
            if(typeof === 'function'){
                then.call(x,(y)=>{ // y是返回promise後的成功結果
                    // 一開始咱們在這裏寫的是resolve(y),可是考慮到一點
                    // 這個y,有可能仍是一個promise,
                    // 也就是說resolve(new Promise(...))
                    // 因此涉及到遞歸,咱們把resolve(y)改爲如下
                    
                    // 12.限制既調resolve,也調reject
                    if(called) return
                    called = true
                    
                    resolvePromise(promise2,y,resolve,reject)
                    // 這樣的話,代碼會一直遞歸,取到最後一層promise
                    
                    // 11.這裏有一種狀況,就是不能既調成功也調失敗,只能挑一次,
                    // 可是咱們前面不是處理過這個狀況了嗎?
                    // 理論上是這樣的,可是咱們前面也說了,resolvePromise這個函數
                    // 是全部promise通用的,也能夠是別人寫的promise,若是別人
                    // 的promise可能既會調resolve也會調reject,那麼就會出問題了,因此咱們接下來要
                    // 作一下限制,這個咱們寫在第12步
                    
                },(err)=>{ // err是返回promise後的失敗結果
                    if(called) return
                    called = true
                    reject(err)
                })
            }else{
                resolve(x) // 若是then不是函數的話,那麼則是普通對象,直接走resolve成功
            }
        }catch(e){ // 當出現異常則直接走reject失敗
            if(called) return
            called = true
            reject(e)
        }
    }else{ // 5.x爲一個常量,則是走resolve成功
        resolve(x)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    // onFulfilled、onRejected是可選參數
    onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
    onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
    let self = this
    let promise2 
    let promise2 = new Promise((resolve,reject)=>{
        if(self.status === 'resolved'){
            // 13.根據promiseA+規範,onFulfilled或onRejected必須
            // 被調用不是當前的上下文,then方法是異步的
            setTimeout(()=>{
                try{
                    let x = onFulfilled(self.value)
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e) 
                }
            },0)
        }
        
        if(self.status === 'rejected'){
            // 同13
            setTimeout(()=>{
                try{
                    let x = onRejected(self.reason) 
                    resolvePromise(promise2,x,resolve,reject)
                }catch(e){
                    reject(e)
                }
            },0)
        }
    
        if(self.status === 'pending'){
            self.onResolvedCallbacks.push(()=>{
                // 同13
                setTimeout(()=>{
                    try{
                        let x =  onFulfilled(self.value)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                },0)
            }) 
            self.onRejectedCallbacks.push(()=>{
                // 同13
                setTimeout(()=>{
                    try{
                        let x = onRejected(self.reason)
                        resolvePromise(promise2,x,resolve,reject)
                    }catch(e){
                        reject(e)
                    }
                },0)
            })
        }
    })
    return promise2
}

// 14.到目前爲止,根據promiseA+規範的代碼寫得差很少了,咱們能夠經過測試代碼來測試咱們是否寫得正確,下面咱們寫一段測試代碼

Promise.defer = Promise.deferred = function(){
    let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve
        dfd.reject = reject
    })
    return dfd
}

// 14.接下來咱們要安裝一個插件,npm install promises-aplus-test -g

module.exports = Promise
複製代碼
// 完整代碼 也順便帶你們理順一下
function Promise(executor) {
    let self = this;
    self.value = undefined;  // 成功的值
    self.reason = undefined;  // 失敗的值
    self.status = 'pending'; // 目前promise的狀態pending
    self.onResolvedCallbacks = []; // 可能new Promise的時候會存在異步操做,把成功和失敗的回調保存起來
    self.onRejectedCallbacks = [];
    function resolve(value) { // 把狀態更改成成功
        if (self.status === 'pending') { // 只有在pending的狀態才能轉爲成功態
            self.value = value;
            self.status = 'resolved';
            self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的成功回調保存起來
        }
    }
    function reject(reason) {  // 把狀態更改成失敗
        if (self.status === 'pending') { // 只有在pending的狀態才能轉爲失敗態
            self.reason = reason;
            self.status = 'rejected';
            self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的失敗回調保存起來
        }
    }
    try {
        // 在new Promise的時候,當即執行的函數,稱爲執行器
        executor(resolve, reject);
    } catch (e) { // 若是執行executor拋出錯誤,則會走失敗reject
        reject(e);
    }
}

// 這個函數爲核心,全部的promise都遵循這個規範
// 主要是處理then中返回的值x和promise2的關係
function resolvePromise(promise2,x,resolve,reject){
    // 當promise2和then返回的值x爲同一個對象時,變成了本身等本身,會陷入死循環
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle'));
    }
    let called;
    // x多是一個promise也多是一個普通值
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
        try{
            let then = x.then; 
            if(typeof then === 'function'){
                then.call(x,y=>{ 
                    if(called) return; 
                    called = true;
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ 
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true;
            reject(e);
        }
    }else{ 
        resolve(x);
    }
}

// then調用的時候,都是屬於異步,是一個微任務
// 微任務會比宏任務先執行
// onFulfilled爲成功的回調,onRejected爲失敗的回調
Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function'?onFulfilled:val=>val;
    onRejected = typeof onRejected === 'function'?onRejected: err=>{throw err}
    let self = this;
    let promise2;
    // 上面講了,promise和jquery的區別,promise不能單純返回自身,
    // 而是每次都是返回一個新的promise,才能夠實現鏈式調用,
    // 由於同一個promise的pending resolve reject只能更改一次
    promise2 = new Promise((resolve, reject) => {
        if (self.status === 'resolved') {
            // 爲何要加setTimeout?
            // 首先是promiseA+規範要求的
            // 其次是你們寫的代碼,有的是同步,有的是異步
            // 因此爲了更加統一,就使用爲setTimeout變爲異步了,保持一致性
            setTimeout(()=>{
                try { // 上面executor雖然使用try catch捕捉錯誤
                      // 可是在異步中,不必定可以捕捉,因此在這裏
                      // 用try catch捕捉
                    let x = onFulfilled(self.value);
                    // 在then中,返回值多是一個promise,因此
                    // 須要resolvePromise對返回值進行判斷
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'rejected') {
            setTimeout(()=>{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'pending') {
            self.onResolvedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
        }
    });
    return promise2
}
Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
module.exports = Promise;
複製代碼
執行promises-aplus-tests promise.js
複製代碼

到此爲止,咱們已經寫了一個符合promiseA+規範的promise了,你們能夠好好多看幾回。


接下來,咱們完善一下這個promise,寫一下經常使用的promise方法

  • Promise.reject
  • Promise.resolve
  • catch
  • Promise.all
  • Promise.race
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason);
    })
}

Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value);
    })
}

Promise.prototype.catch = function(onRejected){
    return this.then(null,onRejected);
};

Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function processData(index,data){
            arr[index] = data;
            if(++i == promises.length){
                resolve(arr);
            }
        }
        for(let i = 0;i<promises.length;i++){
            promises[i].then(data=>{
                processData(i,data);
            },reject);
        }
    })
}

Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0;i<promises.length;i++){
            promises[i].then(resolve,reject);
        }
    })
}
複製代碼

Promise的經常使用方法如何實現呢?

Promise.resolve / Promise.reject
// 原生的Promise.resolve使用
Promise.resolve('hello swr').then((data)=>{ // 直接把成功的值傳遞給下一個then
    console.log(data) // hello swr
})

// 那麼Promise.resolve內部是怎麼實現的呢?
Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{ // 在內部new一個Promise對象
       resolve(value) 
    })
}

// 同理,Promise.reject內部也是相似實現的
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason)
    })
}
複製代碼
catch是怎樣實現呢?
// 原生Promise的catch使用
Promise.reject('hello swr').catch((e)=>{
    console.log(e) // hello swr
})

// 上面這段代碼至關於下面這段代碼
Promise.reject('hello swr').then(null,(e)=>{ // then裏直接走了失敗的回調
    console.log(e) // hello swr
})

// 內部實現
Promise.prototype.catch = function(onRejected){
    return this.then(null,onRejected) // 至關於then裏的成功回調只傳個null
}


複製代碼
Promise.all,這個方法很是重要,同時執行多個異步,而且返回一個新的promise,成功的值是一個數組,該數組成員的順序是傳參給Promise.all的順序
// 原生Promise.all的使用
// 假設1.txt內容爲hello 2.txt內容爲swr
let fs = require('fs')
function read(filePath,encoding){
    return new Promise((resolve,reject)=>{ 
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err)
            resolve(data)
        })
    })
}

Promise.all([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
    console.log(data) // 所有讀取成功後返回 ['hello','swr']
                      // 須要注意的是,當其中某個失敗的話,則會走失敗的回調函數
})

// 內部實現
Promise.all = function(promises){ // promises 是一個數組
    return new Promise((resolve,reject)=>{
        let arr = []
        let i = 0
        function processData(index,data){
            arr[index] = data
            // 5.咱們能用arr.length === promises.length來判斷請求是否所有完成嗎?
            // 答案是不行的,假設arr[2] = 'hello swr'
            // 那麼打印這個arr,將是[empty × 2, "hello swr"],
            // 此時數組長度也是爲3,而數組arr[0] arr[1]則爲空
            // 那麼換成如下的辦法
            if(++i === promises.length){ // 6.利用i自增來判斷是否都成功執行
                resolve(arr) // 此時arr 爲['hello','swr']
            }
        }
        
        for(let i = 0;i < promises.length;i++){ // 1.在此處遍歷執行
            promises[i].then((data)=>{ // 2.data是成功後返回的結果
                processData(i,data) // 4.由於Promise.all最終返回的是一個數組成員按照順序排序的數組
                                    // 並且異步執行,返回並不必定按照順序
                                    // 因此須要傳當前的i
            },reject) // 3.若是其中有一個失敗的話,則調用reject
        }
    })
}
複製代碼
Promise.race 該方法是同時執行多個異步,而後哪一個快,就用哪一個的結果,race的意思是賽跑
// 原生Promise.race的使用
// 一個成功就走成功的回調,一個失敗就走失敗的回調
Promise.race([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{
    console.log(data) // 可能返回 'hello' 也可能返回 'swr' 看哪一個返回快就用哪一個做爲結果
})

// 內部實現
Promise.race = function(promises){ // promises 是一個數組
    return new Promise((resolve,reject)=>{
        for(let i = 0;i < promises.length;i++){ 
            promises[i].then(resolve,reject) // 和上面Promise.all有點相似
        }
    })
}
複製代碼

Promise.defer = Promise.deferred 這個語法糖怎麼理解呢?

這個語法糖能夠簡化一些操做,好比

let fs = require('fs')
// 寫法一:
function read(filePath,encoding){
    // 這裏的new Promise依然是傳遞了一個executor回調函數
    // 咱們該怎樣減小回調函數嵌套呢?
    return new Promise((resolve,reject)=>{ 
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) reject(err)
            resolve(data)
        })
    })
}

// 寫法二:
// 這樣的寫法減小了一層回調函數的嵌套
function read(filePath,encoding){
    let dfd = Promise.defer()
    fs.readFile(filePath,encoding,(err,data)=>{
        if(err) dfd.reject(err)
        dfd.resolve(data)
    })
    return dfd.promise
}

read('./1.txt','utf8').then((data)=>{
    console.log(data)
})


複製代碼

結尾:第一次寫,都是想到哪寫到哪,請你們多多諒解~ 也但願對你們有所幫助,promise的源碼實現,我最大的收穫並非怎麼實現promise,而是編程思惟,你們能夠多多往深裏想想,也但願你們能夠和我進行交流,共同進步

相關文章
相關標籤/搜索