ES6版Promise實現,給你不一樣的體驗

亦曾執著過不後悔發表於2019-03-02
ES6版Promise實現,給你不一樣的體驗

ES6版Promise實現,給你不一樣的體驗

摘要: 在很久很久以前,Promise還沒有來到這個世上。那時森林裡的有這樣一群攻城獅,他們飽受回撥地獄(回撥金字塔)的摧殘,苦不堪言。直到有一天,一位不願意留下姓名的特工橫空出世,將他們從回撥地獄中解救了出來,代號Promise。自此,很多人都踏上了尋找Promise的道路,我亦如此…

ES6版Promise實現,給你不一樣的體驗

友情提醒: 本文使用ES6實現的Promise,不會的童鞋們請自行腦補!What?這位同學你竟然不知道ES6,好的,放學了請不要走,我們單獨交流一下……

回撥地獄 VS Promise

就拿fs(node的核心包)來說吧,假如我們需要同時請求a.txtb.txt中的資料,然後對資料進行操作。這種需求在我們的開發中也經常遇到哦!

  • 曾經的回撥地獄
let fs = require(`fs`);
let arr = [];
fs.readFile(`a.txt`,`utf8`,function(err,data){
  arr.push(data);
  fs.readFile(`b.txt`,`utf8`,function(err,data){
    arr.push(data);
    // 如果需要把更多的檔案資料,那滋味不敢想象
    console.log(arr); 
  })
})
複製程式碼
  • 現在的Promise
let fs = require(`fs`);
function read(url,coding){ // 首先我們對fs.readFile()進行promise封裝
  return new Promise((resolve,reject)=>{
    fs.readFile(url,coding,function(err,data){
      if(err) reject(err);
      resolve(data);
    })
  })
}
Promise.all([read(`a.txt`,`utf8`),read(`b.txt`,`utf8`)]).then(data=>{
  // 這裡我們就可以直接操作請求到的兩個檔案的資料了,Promise還很貼心的返回了一個陣列
  console.log(data);  
})
複製程式碼

相比較之下,Promise和回撥地獄的戰爭起初就不是一個等級的呀,回撥地獄聽起來強大,但實則一點不經揍啊!Promise此時的內心應該是這樣的:

ES6版Promise實現,給你不一樣的體驗

Promise之自己實現

看到這裡,相信大家都很想知道Promise的核心實現是什麼?接下來,請小夥伴們不要閉眼,看這裡,看這裡!我便說說我是如何在尋找Promise的道路上一條道走到黑的。(這標題起的,笑出豬叫聲)

1、Promise 類封裝

起初,我發現Promise是可以被new的,說明Promise 的出身是一個類啊,這可是一條很有價值的線索啊。(大家都知道,還用你說)

class Promise {
  constructor(executor) { // executor是new Promise的引數
    this.status = `pending`; // 儲存狀態
    this.reason = undefined; // 失敗的原因
    this.value = undefined; // 成功的結果
    let resolve = (value)=> {
      if(this.status === `pending`){
        this.status = `resolved`;
        this.value = value;
      }
    }
    let reject = (reason)=>{
      if(this.status === `pending`){
        this.status = `rejected`;
        this.reason = reason;
      }
    }
    try {
      executor(resolve, reject); // 執行器       
    } catch (e) {
      reject(e);
    }
  }
  // 定義例項上的then方法,then呼叫的時候都是非同步呼叫的 
  then(onFulfilled, onRejected) {
    if(this.status === `resolved`){ // status狀態改變時執行onFulFilled
      onFulfilled(this.value);
    }
    if(this.status === `rejected`){ // status狀態改變時執行onFulFilled
      onRejected(this.reason);
    }
  }
}  
複製程式碼

這怎麼僅僅一條線索就寫出來這麼東東呀,真讓人摸不著頭腦!別急,聽我慢慢道來:

  • executor:執行器,預設是new的時候就自動執行,executor的執行是同步的,為什麼要try一下呢,executor執行時如果throw new Error(`error`),直接走reject
  • resolve, reject:定義了executorresolve成功的回撥函式和reject失敗的回撥函式兩個引數
  • reason,value:分別代表了成功返回的值和失敗的原因
  • status:儲存了Promise的三種狀態pending(等待態),fulfilled(成功態),rejected(失敗態)
    • 當一個promise的狀態處於pending時,它可以過渡到fulfilled或者ejected
    • 當一個promise的狀態處於fulfilled時或者rejected時,不能再過渡到其他任何狀態
  • then函式: 因Promise是可以鏈式呼叫的,說明then函式是定義在Promise類的原型Prototype上的。

這樣我們就成功處理了同步情況下的Promise,是不是覺得自己已經追尋到Promise的終極力量了呢。(抽根菸,平復下躁動的心情)

ES6版Promise實現,給你不一樣的體驗

2、Promise非同步的實現

在我們平時的開發中,往往非同步程式碼比較多,非同步執行需要,然而Promiseexecutor執行器又是同步執行的,它不等我們怎麼辦呢,好著急有木有。
我們在上面程式碼的基礎上新增如下幾行程式碼:

class Promise {
  constructor(executor) {
    this.onResolvedCallbacks = []; // 儲存成功的回撥
    this.onRejectedCallbacks = []; // 儲存失敗的回撥
    let resolve = (value)=> {
      if(this.status === `pending`){
        this.status = `resolved`;
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    }
    let reject = (reason)=>{
      if(this.status === `pending`){
        this.status = `rejected`;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }
  }
  then(onFulfilled, onRejected) { 
    if(this.status === `pending`){
      this.onResolvedCallbacks.push(()=>{
          onFulfilled(this.value);
        });
      this.onRejectedCallbacks.push(()=>{
          onRejected(this.reason);
        });
      });
    }
  }
}
複製程式碼

當出現非同步程式碼時,status的狀態還是pending,我們可以先把then函式中成功和失敗的回撥儲存下來,等到非同步程式碼執行完成後,status的狀態改變了,我們再去依次執行儲存下來的回撥函式。

看到這裡,如果覺得自己已經基本掌握Promise的實現,只能說爾等對Promise的核心力量一無所知。(別廢話,趕緊寫)好的,各位大佬!

ES6版Promise實現,給你不一樣的體驗

3、Promise之鏈式呼叫的實現

在開始實現之前呢,我們先來看一下如下程式碼:

//  這裡的Promise是ES6封裝好的,並不是我們自己實現的 
let promise = new Promise((resolve,reject)=>{ 
  resolve(`123`);
})
let promise2 = promise.then((data)=>{
  throw new Error(`error`);
})
promise2.then((data)=>{
  console.log(data);
},(err)=>{
  console.log(err); // 這裡輸出了error
})
複製程式碼

上面程式碼說明then函式執行後返回的promise2例項並不是promise例項,說明返回值不是this,因為promise不能即呼叫成功後不能再走失敗,所以then函式執行後返回的promise2是一個新的promise例項。(跟jQuery的鏈式呼叫不一樣哦)

Promiseconstructor的程式碼不需要改變,只需要對then函式進行再次封裝:

then(onFulfilled, onRejected) {
    // onFulfilled和onRejected可能沒傳
    onFulfilled = typeof onFulfilled === `function` ? onFulfilled : value=>value;
    onRejected = typeof onRejected === `function` ? onRejected : (err)=>{throw err};
    let promise2; // 需要每次呼叫then方法時,都需要返回新的promise
    promise2 = new Promise((resolve, reject)=>{
      if(this.status === `resolved`){
        setTimeout(()=>{
          try {
            let x = onFulfilled(this.value); 
            // 執行完當前回撥後返回的結果可能還是個promise
            resolvePromise(promise2,x,resolve, reject);
          } catch (e) {
            reject(e);
          }
        },0)
      }
      if(this.status === `rejected`){
        setTimeout(()=>{
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2,x,resolve, reject);
          } catch (e) {
            reject(e);
          }
        },0) 
      }
      if(this.status === `pending`){
        this.onResolvedCallbacks.push(()=>{
          setTimeout(()=>{
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2,x,resolve, reject);
            } catch (e) {
              reject(e);
            }
          },0)
        });
        this.onRejectedCallbacks.push(()=>{
          setTimeout(()=>{
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2,x,resolve, reject);
            } catch (e) {
              reject(e);
            }
          },0)
        });
      }
    })
    return promise2;
  }
複製程式碼
  • onFulfilled,onRejected:當沒有傳的時候,需要做的處理
  • promise2then函式的返回值是一個新的promise
  • setTimeout:Promise/A+規範(規範)要求then函式必須是非同步的,當然原生的Promise實現並不是用的setTimeout,而是一個微任務
  • resolvePromise:封裝resolvePromise方法,當then函式中的成功或者失敗函式返回值x可能還是個promise

定義的resolvePromise方法:

let resolvePromise = (promise2,x,resolve, reject)=>{
  let called;
  // promise2和函式的返回值是同一個
  if(promise2 === x){
    return reject(new TypeError(`Chaining cycle detected for promise #<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);// 遞迴處理,直到y是一個普通值
        },(err)=>{
          if(called) return;
          called = true;
          reject(err);
        })
      }else{ // then如果是一個常量
        if(called) return;
        called = true;
        resolve(x);
      }
    } catch (e) {
      if(called) return;
      called = true;
      reject(e);
    }
  }else{ // x如果是一個常量
    if(called) return;
    called = true;
    resolve(x);
  }
}
複製程式碼
  • 四個引數:promise2 (then函式的返回值,是一個新的promise)
    x(then中成功後者失敗函式的返回值)resolve(promise2的resolve)reject(promise2的reject)
  • called: 加了called判斷,防止多次呼叫,因為這裡的邏輯不單單是自己的,還有別人的,別人的promise可能即會呼叫成功也會呼叫失敗
  • let then = x.thenx可能還是一個promise,那麼就讓這個Promise執行

至此,我們終於追尋到了promise的核心力量所在。來,讓我們小小的慶賀一下:

ES6版Promise實現,給你不一樣的體驗

3、Promise之類上的方法實現

當然,我們已經初步瞭解了promise的核心力量,在我們開發的過程中,除了then方法,也會使用它的一些其他常用的方法,就像一位身經百戰的特工,你除了會用刀,還要會用槍不是。我們在Promise類上定義它們:

static resolve(value){
    return new Promise((resolve,reject)=>{
      resolve(value);
    })
  }
  static reject(reason){
    return new Promise((resolve,reject)=>{
      reject(reason);
    })
  }
  static all(promises){
    return new Promise((resolve,reject)=>{
      let arr = [];
      let i = 0;
      let 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);
      }
    })
  }
  static race(promises){
    return new Promise((resolve,reject)=>{
      for(let i = 0; i< promises.length; i++){
        promises[i].then(resolve,reject);
      }
    })
  }
  catch(onRejected){
    return this.then(null,onRejected);
  }
複製程式碼

相信resolve,reject,all,race這四個類上的方法和catch這個原型的方法大家都已經很熟悉了,我就不過多的囉嗦了。

因為,我實在是編不下去了,我還有更重要的事情要去做:

ES6版Promise實現,給你不一樣的體驗

結語: 花了很久寫的這篇文章,如果這篇文章令你或多或少有些收穫,請不要吝嗇你的讚美(點個贊再走嗎,小哥哥小姐姐),如果有寫的不對的地方,也希望各位大佬能不吝指教,萬分感謝!原創文章,轉載請註明出處!

相關文章