promise原理就是這麼簡單

夢想攻城獅發表於2018-09-10

非同步程式設計的幾種形式:

回撥函式形式:

function f1(callback){
    callback();
}
function f2(callback){
    callback();
}
function f3(callback){
    callback();
}
f1(f2(f3))
複製程式碼

這種方式實現非同步程式設計優點是思路清晰,以序列的思考方式進行程式設計,缺點是形成回撥地獄,過多的回撥巢狀使得程式碼變得難以理解拆分和維護。

釋出訂閱模式

let dep = {
  list: [],
  on: function (fn) {
    list.push(fn);
  },
  emit: function () {
    this.list.forEach(event => {
      typeof event === 'function' ? event() : null;
    })
  }
};
複製程式碼

上面就是簡易版的釋出訂閱模式:釋出者存在一個陣列list用於登記訂閱者即非同步執行的函式,等到一定條件下執行emit,訂閱的非同步函式都會執行。這就好比釋出者售樓中心的擁有一個登記冊,裡面登記需要買房的所有訂閱者,有的訂閱者登記的是電話通知,有的訂閱者登記的郵件通知,等到樓盤資訊變化時會主動給每個訂閱者執行相應的操作(執行對應的函式)

promise

promise的核心原理其實就是釋出訂閱模式,通過兩個佇列來快取成功的回撥(onResolve)和失敗的回撥(onReject)。如果還不熟悉promise用法的朋友,請參考Es6入門之promise物件,下面來分析promise的特點。

promise的特點:

  1. new Promise時需要傳遞一個executor執行器,執行器會立刻執行
  2. 執行器中傳遞了兩個引數:resolve成功的函式、reject失敗的函式,他們呼叫時可以接受任何值的引數value
  3. promise狀態只能從pending態轉onfulfilled,onrejected到resolved或者rejected,然後執行相應快取佇列中的任務
  4. promise例項,每個例項都有一個then方法,這個方法傳遞兩個引數,一個是成功回撥onfulfilled,另一個是失敗回撥onrejected
  5. promise例項呼叫then時,如果狀態resolved,會讓onfulfilled執行並且把成功的內容當作引數傳遞到函式中
  6. promise中可以同一個例項then多次,如果狀態是pengding 需要將函式存放起來 等待狀態確定後 在依次將對應的函式執行 (釋出訂閱)

promise基礎版實現

下面針對這些特點來實現promise:

new Promise時需要傳遞一個executor執行器,執行器會立刻執行;執行器中傳遞了兩個引數:resolve成功的函式、reject失敗的函式,他們呼叫時可以接受任何值的引數value
function Promise (executor){
    function resolve(value){}
    function reject(value){}
    try{
        executor(resolve,reject);
    }catch(e){
        reject(e);
    }
}
var promise = new Promise((resolve,reject)=>{
    console.log('start');
})
複製程式碼
promise狀態只能從pending態轉onfulfilled,onrejected到resolved或者rejected
function Promise (executor) {
  var self = this;//resolve和reject中的this指向不是promise例項,需要用self快取
  self.state = 'padding';
  self.value = '';//快取成功回撥onfulfilled的引數
  self.reson = '';//快取失敗回撥onrejected的引數
  self.onResolved = []; // 專門存放成功的回撥onfulfilled的集合
  self.onRejected = []; // 專門存放失敗的回撥onrejected的集合
  function resolve (value) {
    if(self.state==='padding'){
      self.state==='resolved';
      self.value=value;
      self.onResolved.forEach(fn=>fn())
    }
  }
  function reject (reason) {
    self.state = 'rejected';
    self.value = reason;
    self.onRejected.forEach(fn=>fn())
  }
  try{
    executor(resolve,reject)
  }catch(e){
    reject(e)
  }
}
複製程式碼
promise例項,每個例項都有一個then方法,這個方法傳遞兩個引數,一個是成功回撥onfulfilled,另一個是失敗回撥onrejected;
promise例項呼叫then時,如果狀態resolved,會讓onfulfilled執行並且把成功的內容當作引數傳遞到函式中;
promise中可以同一個例項then多次,如果狀態是pengding 需要將函式存放起來 等待狀態確定後 在依次將對應的函式執行(釋出訂閱)
Promise.prototype.then=function (onfulfilled,onrejected) {
  var self=this;
  if(this.state==='resolved'){
    onfulfilled(self.value)
  }
  if(this.state==='rejected'){
    onrejected(self.value)
  }
  if(this.state==='padding'){
    this.onResolved.push(function () {
      onfulfilled(self.value)
    })
  }
}
複製程式碼

then方法的特點:

以上只是實現了promise的基本功能,但是還缺少then的鏈式呼叫,then函式引數onfulfilled,onrejected預設處理,鏈式呼叫返回值多種情況的處理,下面分析then方法的特點:

  1. 因為promise狀態確定後就是不能更改,所以每次promise執行then後都會返回一個新的promise而不是this,那麼狀態永遠為resolve或jeject,將存在非同步呼叫
  2. onfulfilled或onrejected是一個可選引數,需要做沒有傳遞時的處理
  3. 如果then中onfulfilled或onrejected返回的是一個普通值的話會把這個結果傳遞下一次then中的成功回撥
  4. 如果then中onfulfilled或onrejected出現異常,會走下一個then的失敗回撥,將err傳遞到失敗回撥中
  5. 如果失敗後還可以成功,如果返回undefined,會把undefined傳遞給下一次
  6. 如果then方法返回的是一個promise,那麼會等待這個promise執行完決定返回的是成功還是失敗

promise優化版實現

因為promise狀態確定後就是不能更改,所以每次promise執行then後都會返回一個新的promise而不是this,那麼狀態永遠為resolve或jeject,將存在非同步呼叫;onfulfilled或onrejected是一個可選引數,需要做沒有傳遞時的處理
Promise.prototype.then=function (onfulfilled,onrejected) {
    onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val => val;//onfulfilled預設處理
    onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err;};//onrejected預設處理
  var self=this,promise2=new Promise(function(resolve,reject){//返回一個promise
    if(this.state==='resolved'){
    try{
        onfulfilled(self.value);//裡面會執行resolve    
    }catch(e){
        reject(e);
    }
  }
  if(this.state==='rejected'){
    try{
        onrejected(self.value);
    }catch(e){
        reject(e);
    }
  }
  if(this.state==='padding'){//將執行過程快取
   self.onResolved.push(function () {
        try{
            onfulfilled(self.value);
        }catch(e){
           reject(e)
        }
    });
    self.onRejected.push(function () {
        try{
            onrejected(self.value);
        }catch(e){
            reject(e)
        }
    })
  }  
  })
  return promise2;
}
複製程式碼
如果then中onfulfilled或onrejected返回的是一個普通值的話會把這個結果傳遞下一次then中的成功回撥;
如果then中onfulfilled或onrejected出現異常,會走下一個then的失敗回撥,將err傳遞到失敗回撥中;
如果失敗後還可以成功,如果返回undefined,會把undefined傳遞給下一次;
如果then方法返回的是一個promise,那麼會等待這個promise執行完決定返回的是成功還是失敗,所以需要對onfulfilled或onrejected的返回值進行處理。
Promise.prototype.then=function (onfulfilled,onrejected) {
    onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val => val;
    onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err;};
  var self=this,
    res=null,//用來快取onfulfilled或onrejected的返回值
    promise2=new Promise(function(resolve,reject){
    if(this.state==='resolved'){
    try{
        res = onfulfilled(self.value);//得到onfulfilled的返回值
        resolvePromise(promise2,res,resolve,reject);//返回值的處理函式
    }catch(e){
        reject(e);
    }
  }
  if(this.state==='rejected'){
    try{
        res = onrejected(self.value);//得到onrejected的返回值
        resolvePromise(promise2,res,resolve,reject);
    }catch(e){
        reject(e);
    }
  }
  if(this.state==='padding'){
   self.onResolved.push(function () {
        try{
            res = onfulfilled(self.value);
            resolvePromise(promise2,res,resolve,reject);
        }catch(e){
           reject(e)
        }
    });
    self.onRejected.push(function () {
        try{
            res = onrejected(self.value);
            resolvePromise(promise2,res,resolve,reject);
        }catch(e){
            reject(e)
        }
    })
  }  
  })
  return promise2;
}
function resolvePromise(promise,res,resolve,reject) {
  if(promise===res){//防止迴圈引用
    return reject(new TypeError('迴圈引用'))
  }
  let called;//防止重複執行
  if(res!==null&&(typeof res==='function'||typeof res ==='object')){
    try {//防止promise執行報錯
      let then=res.then;//判斷是否promise就判斷是否存在then方法
      if(typeof then ==='function'){//如果返回的是promise,只需要在返回的promise的then方法中下一步需要執行的函式
        then.call(res,(res2)=>{
          if (called) return;
          called = true;
          resolvePromise(promise,res2,resolve,reject);//如果是promise繼續遞迴執行,直到不是promise,依次執行外層的resolve,讓promise狀態改變
        },)
      }else{//如果不是promise,有可能是undefine、onfulfilled或onrejected的返回的普通值,就直接將這個值返回,將外層的promise狀態改變
        if (called) return;
        called = true;
        resolve(then)
      }
    }catch(e){
      if (called) return;
      called = true;
      reject(e)
    }
  }else{
    resolve(res)
  }
};
複製程式碼
promise.then屬於非同步微任務,then中的方法,必須等到所有的同步任務執行完才執行,巨集任務和微任務可以檢視EventLoop其實如此簡單
console.log(1);
var promise=new Promise(function(resolve,reject){
    resolve('a');
})
promise.then(function(value){
    console.log(value)
})
console.log(2);
// 1 2 'a'
複製程式碼
//由於promise有內部的機制實現微任務,所以這裡使用setTimeout代替
Promise.prototype.then=function (onfulfilled,onrejected) {
    onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val => val;
    onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err;};
  var self=this,
    res=null,
    promise2=new Promise(function(resolve,reject){
    if(this.state==='resolved'){
        setTimeout(()=>{
            try{
                res = onfulfilled(self.value);
                resolvePromise(promise2,res,resolve,reject);
            }catch(e){
                reject(e);
            }
        })
    }
  if(this.state==='rejected'){
        setTimeout(()=>{
            try{
                res = onrejected(self.value);
                resolvePromise(promise2,res,resolve,reject);
            }catch(e){
                reject(e);
            }  
        })
    }
  if(this.state==='padding'){
    self.onResolved.push(function () {
        setTimeout(()=>{
            try{
                res = onfulfilled(self.value);
                resolvePromise(promise2,res,resolve,reject);
            }catch(e){
                reject(e);
            }
        })
    });
    self.onRejected.push(function () {
        setTimeout(()=>{
            try{
                res = onrejected(self.value);
                resolvePromise(promise2,res,resolve,reject);
            }catch(e){
                reject(e);
            }  
        })
    })
  }  
  })
  return promise2;
}
複製程式碼

promise.catch會捕獲到沒有捕獲的異常;

Promise.resolve會返回一個狀態位rsolved的promise;

Promise.reject會返回一個狀態位rsolved的promise;

Promise.all會在所有的promise resolved後執行回撥,Promise.race只要有一個promise resolved就執行回撥。

promise.catch將所有的錯誤在promise例項的下一個then中返回
Promise.prototype.catch = function (onrejected) {
  return this.then(null, onrejected)
};
複製程式碼
Promise.reject和Promise.reject
Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason)
  })
};
Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    resolve(value);
  })
};
複製程式碼
Promise.all和Promise.race
//在每個promise的回撥中新增一個判斷函式processData(就是在當前的promise.then中新增),每個promise狀態改變後讓index++,直到和promises的個數相等就執行回撥
Promise.all=function (promises) {
  return new Promise((resolve,reject)=>{
    let results=[],i=0;
    for(let i=0;i<promises.length;i++){
      let p=promises[i];
      p.then((data)=>{
        processData(i,data)
      },reject)
    }
    function processData (index,data) {
      results[index]=data;
      if(++i==promises.length){
        resolve(results)
      }
    }
  })
};
//在每個promise的回撥中新增一個resolve(就是在當前的promise.then中新增),有一個狀態改變,就讓race的狀態改變
Promise.race=function (promises) {
  return new promises((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      let p=promises[i];
      p.then(resolve,reject)
    }
  })
};
複製程式碼

generator + co

Generator函式可以通過yield暫停執行和next恢復執行,所以可以封裝一個函式來自動執行next函式而使Generator完成非同步任務。

let fs = require('mz/fs');
function * read() {
  let age = yield fs.readFile('./name.txt','utf8');
  let adress = yield fs.readFile(age,'utf8');
  let r = yield fs.readFile(adress,'utf8');
  return r;
}
function co(it) {//it為一個generator物件
//返回一個promise並且執行generator物件的next
return new Promise((resolve,reject)=>{
    function next(data) {
        //獲取前一個非同步函式的返回值,其必須為promise
        let { value, done } = it.next(data);  
            if(!done){
            //返回promise的then方法中新增generator的next
            //前一個非同步函式執行成功會使返回的promise成resolved
            //然後執行generator的next,遞迴依次類推
            value.then(data=>{  
                next(data)
            }, reject);
        }else{
            resolve(value);
        }
    }
        next();
  })
}
co(read()).then(data=>{
  console.log(data);
},err=>{
  console.log(err);
});
複製程式碼

async和awit

async+awit等於generator+co,而co中實現generator自動化是基於Promise,所以awit會使用new Promise形式。

結語

如果以上有任何錯誤之處,希望提出並請指正,如果對promise使用還不清楚的朋友,請參考Es6入門之promise物件,本文參考:

  1. Promise A+規範
  2. Es6入門之promise物件
  3. Promise 原始碼
  4. 教你一步步實現promise

相關文章