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

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

摘要: 在好久好久之前,Promise尚未來到這個世上。那時森林裏的有這樣一羣攻城獅,他們飽受回調地獄(回調金字塔)的摧殘,苦不堪言。直到有一天,一位不肯意留下姓名的特工橫空出世,將他們從回調地獄中解救了出來,代號Promise。自此,不少人都踏上了尋找Promise的道路,我亦如此... node

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

回調地獄 VS Promise

就拿fs(node的核心包)來講吧,假如咱們須要同時請求a.txtb.txt中的數據,而後對數據進行操做。這種需求在咱們的開發中也常常遇到哦!promise

  • 曾經的回調地獄
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此時的心裏應該是這樣的: bash

Promise之本身實現

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

一、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);
    }
  }
}  
複製代碼

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

  • 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的終極力量了呢。(抽根菸,平復下躁動的心情)this

二、Promise異步的實現

在咱們平時的開發中,每每異步代碼比較多,異步執行須要,然而Promiseexecutor執行器又是同步執行的,它不等咱們怎麼辦呢,好着急有木有。 咱們在上面代碼的基礎上新增以下幾行代碼:url

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的狀態改變了,咱們再去依次執行保存下來的回調函數。spa

看到這裏,若是以爲本身已經基本掌握Promise的實現,只能說爾等對Promise的核心力量一無所知。(別廢話,趕忙寫)好的,各位大佬!

三、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的核心力量所在。來,讓咱們小小的慶賀一下:

三、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這個原型的方法你們都已經很熟悉了,我就不過多的囉嗦了。

由於,我實在是編不下去了,我還有更重要的事情要去作:

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

相關文章
相關標籤/搜索