Promise對象

首先先理解如下幾個概念的概念:git

同步:一次只能執行一次任務,這個任務執行完以後才能執行下一個,它會阻塞其餘任務。es6

異步:能夠一塊兒執行多個任務。github

回調地獄:回調函數的層層嵌套,致使代碼層次過多,很差理解和維護。面試

1、Promise的含義

先簡單瞭解如下Promise的含義和特性:編程

  • Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更加合理和強大;segmentfault

  • 所謂的Promise,簡單來講就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果;數組

  • 從語法上來講,Promise是一個對象,從它能夠獲取異步操做的消息;promise

  • Promise對象表明的是一個異步操做,有三種狀態: pending(等待狀態)、 fulfilled(成功狀態)、 rejected(失敗狀態)markdown

Promise對象的特色:

1. 對象的狀態不收外界的影響;併發

只有異步操做的結果才能決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。

2. 一旦狀態改變就不會再變;

Promise對象狀態改變只有兩種可能:

  • 從pending變爲fulfilled
  • 從pengding變爲rejected

只要這兩種狀態發現了,狀態就凝固了,不會再變了,會一直保持這個結果,這時稱爲resolved(已定型)

3. 每一個Promise實例都有一個then方法,一個Promise能夠.then多個;

4. 每次執行Promise的時候,都會返還一個新的Promise實例;

有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,解決了異步層層嵌套的函數(簡稱回調地獄)問題。

說到 Promise,咱們首先想到的最核心的功能就是異步鏈式調用。

Promise對象的缺點:

  1. 沒法取消Promise。一旦新建它就會當即執行,沒法中途取消;

  2. 若是不設置回調函數,Promise內部拋出錯誤,不會反應到外部;

  3. 當處於Pending(進行中)狀態時,沒法得知目前進展到哪個節點(剛開始仍是即將完成);

使用Promise對象的優勢:

  1. 能夠解決異步嵌套問題(回調地獄);
  2. 能夠解決多個異步併發問題;

2、基本用法

注意,爲了行文方便,本章後面的resolved統一隻指fulfilled狀態,不包含rejected狀態。

一、創造一個Promise實例

ES6規定,Promise對象時一個構造函數,使用new來生成Promise實例。

let promise = new Promise(() => {//executor執行器,特色是當即執行

})
複製代碼

這個Promise實例是一個類,容許接受一個函數做爲參數,這個函數叫作executor執行器,特色是當即執行。以下會當即打印:

let promise = new Promise(() => {//executor執行器,做用當即執行
    console.log("當即執行");//Promise有三種狀態,此處默認Pending狀態
}) 
console.log("222");

/*控制檯打印*/

//當即執行
//222
複製代碼

二、實例接受兩個參數resolve,rejected,使用then

函數的能夠接受兩個參數resolve(表明成功狀態)和rejected(表明失敗狀態),並且每一個Promise實例都有一個

then方法。(注意:resolve和rejected必須配合then一塊兒使用,否則會打印空值)

then分別放置了兩個函數:

  • onfulfilled 實例成功要執行的邏輯
  • onrejected 實例失敗要執行的邏輯
let promise = new Promise((resolve,rejected) => {
  resolve('成功');//若是這裏調用的是resolve,那邊then就會走成功的邏輯;
}).then(data => {//成功
  console.log(data);
},err => {//失敗
  console.log(err);
})

/*控制檯打印*/
//成功


let promise = new Promise((resolve,rejected) => {
  rejected('失敗');//若是這裏調用的是rejected,那邊then就會走失敗的邏輯;
}).then(data => {//成功
  console.log(data);
},err => {//失敗
  console.log(err);
})

// 失敗


//上邊的代碼也能夠這樣寫:
let promise = new Promise((resolve, rejected) => {
    resolve('成功');
})
promise.then(data => {
    console.log(data);
}, err => {
    console.log(err);
})
複製代碼

由於Promise實例的狀態一旦狀態改變就不會再變,因此調用resolve以後再調用rejected,後一步是不會生效的。反之亦然。

let promise = new Promise((resolve,rejected) => {
  resolve('成功');
  rejected('失敗');//這一步不會執行
}).then(data => {//成功
  console.log(data);
},err => {//失敗
  console.log(err);
})

//成功
複製代碼

若是Promise實例內部報錯,就會變成失敗狀態,不會執行後邊的方法:

let promise = new Promise((resolve, rejected) => {//executor執行器,做用當即執行
    throw new Error('內部拋出錯誤');//內部拋出錯誤,就會變成失敗狀態,then就會走失敗的邏輯
    resolve('成功');//這一步依然不會執行
}).then(data => {//成功
    console.log(data);
}, err => {//失敗
    console.log(err);
})

//Error: 內部拋出錯誤
// ...
複製代碼

3、Promise實例的原理

一、手寫一個基礎版的Promise

新建一個promise.js,並新建一個Promise類,並導出:

//promise.js:
class Promise {

};
module.exports = Promise;//導出
複製代碼

將前面的代碼引入promise.js:

//callBack.js:
let Promise = require('./promise');
//引入自定義的Promise,至關於下面用的Promise就是自定義的Promise

let promise = new Promise((resolve, rejected) => {
    resolve('成功');
}).then(data => {
    console.log(data);
}, err => {
    console.log(err);
})
複製代碼

根據callBack.js的Promise實例,自定義一個基礎版的Promise,不考慮其餘異常狀況:

(1)建立公共的then方法

Promise實例有三種狀態,不論是哪種狀態都會執行的then方法,因此then方法屬於公共的屬性。

//promise.js:
class Promise {
  then(){
  
  }
};
module.exports = Promise;//導出
複製代碼

每個Promise實例都有本身的三個狀態,因此將三種狀態放到對應的構造函數(constructor)中。

在構造函數中能夠拿到Promise實例的狀態,由於這個三個狀態會常常用到,因此咱們能夠把它們存成一個常量。

//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
  
  constructor(){//構造函數
    this.status = PENDING;//Promise實例的默認pending狀態
  }
  
  then(){
  
  }
};
module.exports = Promise;//導出
複製代碼

咱們在callBacks.js的new Promise實例時,傳了一個函數(也就是一個執行器),而且這個函數是當即執行的,因此咱們要給構造函數也傳遞一個executor執行器:

//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
  
  constructor(executor){//構造函數
    this.status = PENDING;//Promise實例的默認pending狀態
    
    executor();//默認執行器會當即執行
  }
  
  then(){
  
  }
};
module.exports = Promise;//導出
複製代碼

callBacks.js的new Promise實例時,executor執行的時候傳遞了兩個函數,而且屬於當前的Promise實例,不須要再then中拿到,因此咱們能夠在constructor中聲明兩個函數:成功函數和失敗函數。而且將這兩個函數傳遞給executor執行器:

//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
  
  constructor(executor){//構造函數
    this.status = PENDING;//Promise實例的默認pending狀態
    //成功函數
    let resolve = () => {
      
    }
    //失敗函數
    let reject = () => {
    
    }
    
    
    executor(resolve,reject);//默認執行器會當即執行
  }
  
  then(){
  
  }
};
module.exports = Promise;//導出
複製代碼

成功函數和失敗的函數都接受一個值,value和reason,由於在then也須要用到這兩個值,因此咱們須要定義一個變量。咱們在執行完成功和失敗函數以後,須要更新Promise的狀態:

//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
  
  constructor(executor){//構造函數
    this.status = PENDING;//Promise實例的默認pending狀態
    
    this.value = undefined;//成功的值
    this.reason = undefined;//失敗的緣由
    
    //成功函數
    let resolve = (value) => {
      this.value = value;
      this.status = RESOLVED;//Promise實例狀態更新爲成功狀態:resolved狀態
    }
    
    //失敗函數
    let reject = (reason) => {
      this.reason = reason;
      this.status = REJECTED;//Promise實例狀態更新爲失敗狀態:rejected狀態
    }
    
    
    executor(resolve,reject);//默認執行器會當即執行
  }
  
  then(){
  
  }
};
module.exports = Promise;//導出
複製代碼

由於Promise實例的狀態一旦改變了就不能再變了,也就是說咱們調用了resolve函數就不能再調用reject函數了,因此咱們必須添加添加狀態判斷,只要狀態時等待狀態pending時,才執行函數:

//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
  
  constructor(executor){//構造函數
    this.status = PENDING;//Promise實例的默認pending狀態
    
    this.value = undefined;//成功的值
    this.reason = undefined;//失敗的緣由
    
    //成功函數
    let resolve = (value) => {
      if(this.status === PENDING){//// 屏蔽調用,調了成功函數以後不能調失敗函數
        this.value = value;
        this.status = RESOLVED;//調用成功以後,Promise狀態變成resolved狀態
      }
    }
    
    //失敗函數
    let reject = (reason) => {
      if(this.status === PENDING){
        this.reason = reason;
        this.status = REJECTED;////調用失敗以後,Promise狀態變成rejected狀態
      }
    }
    
    
    executor(resolve,reject);//默認執行器會當即執行
  }
  
  then(){
  
  }
};
module.exports = Promise;//導出
複製代碼

由於執行器執行的時候內部可能會報錯,會拋出異常,這個時候添加try...catch進行邏輯處理,若是內部拋出異常的話至關於調用了reject失敗函數:

img

接下來開始調用then( )方法,then能夠放置了兩個函數:

  • onfulfilled 實例成功要執行的邏輯
  • onrejected 實例失敗要執行的邏輯

那麼何時調用哪個方法呢?這要根據Promise實例的狀態來判斷了:

img

Promise實例還有訂閱發佈功能:

未完待續,後續更新~~

promise.js完整代碼:

//promise.js:
const PENDING = "PENDING";//等待狀態
const RESOLVED = "RESOLVED";//成功狀態
const REJECTED = "REJECTED";//失敗狀態
class Promise {
  
  constructor(executor){//構造函數
    this.status = PENDING;//Promise實例的默認pending狀態
    
    this.value = undefined;//成功的值
    this.reason = undefined;//失敗的緣由
    
    this.onResolveCallBacks = [];//成功的回調的數組
    this.onRejectCallBacks = [];//失敗的回調的數組
    
    //成功函數
    let resolve = (value) => {
      if(this.status === PENDING){//// 屏蔽調用,調了成功函數以後不能調失敗函數
        this.value = value;
        this.status = RESOLVED;//調用成功以後,Promise狀態變成resolved狀態
         this.onResolveCallBacks.forEach(fn => fn())//發佈
      }
    }
    
    //失敗函數
    let reject = (reason) => {
      if(this.status === PENDING){
        this.reason = reason;
        this.status = REJECTED;////調用失敗以後,Promise狀態變成rejected狀態
        this.onRejectCallBacks.forEach(fn => fn())//發佈
      }
    }
    
    //執行器執行時,可能內部報錯:
    try{
        executor(resolve,reject);//默認執行器會當即執行,接受resolve,reject做爲參數
    }catch(error){
      reject(error);//若是執行器執行時發生錯誤,等價於調用了失敗方法
    }
    
  }
  
  then(onfulfilled,onrejected){/// then 目前有兩個參數:onfulfilled,onrejected
    //同步狀況:成功以後執行邏輯
    if(this.status === RESOLVED){
       onfulfilled(this.value);
    }
    
    //同步狀況:失敗以後執行邏輯
    if(this.status === PENDING){
       onrejected(this.reason);
     }
    
     // 若是是異步就先訂閱號
    if (this.status === PEDING) {
      this.onResolveCallBacks.push(() => {
        // todo
        onfulfilled(this.value)
      })

      this.onRejectCallBacks.push(() => {
        // todo
        onrejected(this.value)
      })
        }
  }
};
module.exports = Promise;//導出
複製代碼

5、常見的Promise面試題

一、實現函數sleep,先輸出A,1秒以後輸出B,有什麼方案嗎?

(1)經過Promise實現:

console.log('A');
function sleep(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  })
};

sleep(1000).then(() => {
  console.log('B');
});
        
//先輸出A,延遲1秒以後輸出B

//或
console.log('A');
const sleep = ((time)=>{
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve();
    },time)
  })
})
sleep(1000).then(()=>{
  console.log('B');
})
複製代碼

(2)經過async/awiat進行實現:

const sleep = ((time)=>{
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve();
    },time)
  })
})

async function sleepAsync(){
  await sleep(1000);
  console.log('B');
}
複製代碼

(3)從Generator配合yield進行實現

console.log("A");
const sleep = ((time)=>{
  return new Promise((resolive)=>{
    setTimeout(()=>{
      resolve();
    },time)
  })
})

function* sleepGenerator(time){
  yeild sleep(time);
}

sleepGenerator(1000).next().value.then(()=>{
  console.log("B");
})
複製代碼

類似的面試題:

常常有業務需求,要等幾秒才進行下一步操做,也是使用以上的思路

二、紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個燈不斷交替重複亮燈?(用Promse實現+遞歸)

三個亮燈函數已經存在:

思路:

紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次,意思就是3秒,執行一次 red 函數,2秒執行一次 green 函數,1秒執行一次 yellow 函數,不斷交替重複亮燈,意思就是按照這個順序一直執行這3個函數,這步能夠就利用遞歸來實現。

function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}

const sleep = ((time,fn)=>{
  return new Promise((resolve)=>{
    setTimeout(()=>{
      fn();//哪一個燈亮
      resolve();
    },time)
  })
})

let step = (()=>{
  Promise.resolve().then(()=>{
    return sleep(3000,red);
  }).then(()=>{
    return sleep(2000,green);
  }).then(()=>{
    return sleep(1000,yellow);
  }).then(()=>{
    step();
  })
  
})
複製代碼

三、輸出下面的執行結果:

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
  //輸入1
複製代碼

Promise.resolve 方法的參數若是是一個原始值,或者是一個不具備 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態爲resolved,Promise.resolve 方法的參數,會同時傳給回調函數。

then 方法接受的參數是函數,而若是傳遞的並不是是一個函數,它實際上會將其解釋爲 then(null),這就會致使前一個 Promise 的結果會穿透下面。

6、Promise的應用場景

...

學習寫做中,持續補充更新,記錄很差的地方望指出修改,共同進步~

參考資料:

ECMAScript 6 入門-Promise

關於Promise的面試題

手寫Promise20行

剖析Promise內部結構,一步一步實現一個完整的、能經過全部Test case的Promise類

手寫Promise

相關文章
相關標籤/搜索