深入淺出Promise

Qsy發表於2021-06-08

Abstract

Promise的意思是承諾(在紅寶書中翻譯為期約),新華字典:(動)對某項事務答應照辦。

Promise最早出現在Commn JS,隨後形成了Promise/A規範。

Promise是非同步程式設計的一種解決方案,簡單講是一個容器其中儲存這某個未來才會結束的事件的結果,從語法上說,Promise物件可以獲取非同步操作的訊息,而且Promise提供了統一的API對於各種非同步操作都可以用同樣的方法進行處理

比如我們發出請求呼叫伺服器資料,由於網路延時原因,我們此時無法呼叫到資料,我們可以接著執行其它任務,等到將來某個時間節點伺服器響應資料到達客戶端,我們即可使用promise自帶的一個回撥函式來處理資料,所以要想真正理解Promise我們必須從回撥開始

1、回撥函式定義

回撥是一個在另一個函式完成執行後所執行的函式——故此得名“回撥”。

2、為什麼需要回撥

JavaScript 是一種事件驅動的單執行緒語言。這意味著,在繼續之前, JavaScript 不會等待響應,而是繼續執行且同時監聽事件。

舉例:

function first(){
  console.log(1);
}
function second(){
  console.log(2);
}
first();
second();
//1
//2

如果再first中有計時器呢?

function first(){
  // 模擬程式碼延遲
  setTimeout( function(){
    console.log(1);
  }, 500 );
}
function second(){
  console.log(2);
}
first();
second();
//2
//1
//並不是 JavaScript 沒有按照我們想要的順序執行我們的函式,而是在繼續執行 second() 之前, JavaScript 沒有等待 first() 的響應。

1.回撥函式字面意思:就是回撥就是一個函式的呼叫過程。那麼就從理解這個呼叫過程開始吧。函式a有一個引數,這個引數是個函式b,當函式a執行完以後執行函式b。那麼這個過程就叫回撥。

2.回撥函式的意義:
非同步:一般ajax請求都是非同步的。請求發出去後,處理器會繼續執行下面的程式碼。如果你想ajax請求完成後,做一些事情,顯然,直接在下一行寫程式碼是達不到目的。而作為回撥函式傳給ajax請求,可以控制請求在哪個階段去呼叫回撥函式,並且不用擔心後面的程式碼執行到什麼地方了。

3.回撥函式的執行:就是非同步的函式體執行成功或失敗呼叫的傳遞進來的函式,呼叫的函式就是回撥,為了不影響程式碼執行的效率,我們不會等待非同步的程式碼執行,而是直接像下面執行,但是像請求,計時器,事件等操作我們在一些情況下必須拿到對應的資料或者返回值,這個時候就可以在非同步函式裡傳入一個回撥函式,也就是在非同步操作執行結束之後會呼叫這個回撥函式執行一段程式碼

3.回撥的侷限性

//普通函式
// 第一步,開啟冰箱
function open(){
    setTimeout(()=>{
        console.log('開啟冰箱');
        return 'success';
    }, 1000)
}

// 第二步,放牛進去
function settle(){
      setTimeout(()=>{
       console.log('放牛進去');
       return 'success';
    }, 3000)
}

// 第三步,關上冰箱
function close(){
      setTimeout(()=>{
       console.log('關上冰箱');
       return 'success';
    }, 1000)
}

function closeCow(){
    open();
    settle();
    close()
}

closeCow();

//"開啟冰箱"
//"關上冰箱"?
//"放牛進去"?







//回撥函式實現
function closeCow() {
    setTimeout(() => {
        console.log("開啟冰箱");
        setTimeout(() => {
            console.log("放牛進去");
            setTimeout(() => {
                console.log("關閉冰箱");
            }, 1000);
        }, 3000);
    }, 1000);
}

如何解決回撥巢狀?

1.保持你的程式碼簡短(給函式取有意義的名字,見名知意,而非匿名函式,寫成一大坨)

2.模組化(函式封裝,打包,每個功能獨立,可以單獨的定義一個js檔案Vue,react中通過import匯入就是一種體現)

3.Promise/生成器/ES6等

4.Promise的特點

特點1

Promise有三種狀態,分別是pending(進行中)、fulfilled(已成功)、rejected(已失敗)。只有非同步操作的結果可以決定當前是哪一種狀態,任何其他操作都無法改變這個操作,Promise(承諾)這個名字也是由此而來,表示其他手段無法改變狀態

特點2

如果狀態發生了改變,就不會再改變而且任何時候都可以得到這個結果,Promise狀態的改變只有兩種情況一種是變為fulfilled另一種是變為rejected,改變後狀態就凝固了不會再有任何改變,會一直保持這個結果,這是就成為resolved(已定形)。而且,如果改變已經發生你也可以對Promise新增回撥函式獲得結果,這與事件有根本的區別,事件如果不監聽(dom.addEventListener),錯過之後就無法再得到結果

特點3

無法取消Promise一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)

5.Promise的使用

基礎語法

const promise = new Promise((resolve, reject) => {
    resolve('fulfilled...'); // 狀態由 pending --> fulfilled
});

promise.then(res => {
    console.log(res); // 只會呼叫 resolve
}, err => {
    console.log(err); // 不會呼叫 rejected
})
// fulfilled

Promise

1.特性:

​ 立即執行,自身是非同步

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// ?
// ?
// ?
上面程式碼中,Promise 新建後立即執行,所以首先輸出的是Promise。然後,then方法指定的回撥函式,將在當前指令碼所有同步任務執行完才會執行,所以resolved最後輸出。

2.特性

​ 值穿透

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

// 輸出 ?
//Promise的then方法的引數期望是函式,傳入非函式則會發生值穿透。

6.Promise API

比較簡單,

建議檢視MDN文件

7.手寫Promise

核心點

1.狀態的改變

2.值的改變

3.返回值

4.class的使用

基礎版

// 構建
const PENDING = 'pending'//進行中
const FULFILLED = 'fulfilled'//已成功
const REJECTED = 'rejected'//已失敗

class NewPromise {
    //接受一個函式handle
  constructor (handle) {
  if (!isFunction(handle)) {
    throw new Error('MyPromise must accept a function as a parameter')
  }
  // 新增狀態
  this._status = PENDING
  // 新增狀態
  this._value = undefined
  // 新增成功回撥函式佇列
  this._fulfilledQueues = []
  // 新增失敗回撥函式佇列
  this._rejectedQueues = []
  // 執行handle
  try {
      //執行回撥函式,class是嚴格模式指向undefined,通過bind修改指向resove的指向
    handle(this._resolve.bind(this), this._reject.bind(this)) 
      //收集錯誤
  } catch (err) {
    this._reject(err)
  }
 }
}




//then方法的對應狀態的回撥執行時機,值的改變
then (onFulfilled, onRejected) {
  const { _value, _status } = this
  switch (_status) {
    // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行
    case PENDING:
      this._fulfilledQueues.push(onFulfilled)
      this._rejectedQueues.push(onRejected)
      break
    // 當狀態已經改變時,立即執行對應的回撥函式
    case FULFILLED:
      onFulfilled(_value)
      break
    case REJECTED:
      onRejected(_value)
      break
  }
  // 返回一個新的Promise物件
  return new MyPromise((onFulfilledNext, onRejectedNext) => {
  })
}









//那返回的新的 Promise 物件什麼時候改變狀態?改變為哪種狀態呢?
//then方法確認Promise狀態
then (onFulfilled, onRejected) {
  const { _value, _status } = this
  // 返回一個新的Promise物件
  return new MyPromise((onFulfilledNext, onRejectedNext) => {
    
  
      
      
      // 封裝一個成功時執行的函式
    let fulfilled = value => {
      try {
        if (!isFunction(onFulfilled)) {
          onFulfilledNext(value)
        } else {
          let res =  onFulfilled(value);
          if (res instanceof MyPromise) {
            // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥
            res.then(onFulfilledNext, onRejectedNext)
          } else {
            //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式
            onFulfilledNext(res)
          }
        }
      } catch (err) {
        // 如果函式執行出錯,新的Promise物件的狀態為失敗
        onRejectedNext(err)
      }
    }
    
    
    // 封裝一個失敗時執行的函式
    let rejected = error => {
      try {
        if (!isFunction(onRejected)) {
          onRejectedNext(error)
        } else {
            let res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥
              res.then(onFulfilledNext, onRejectedNext)
            } else {
              //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式
              onFulfilledNext(res)
            }
        }
      } catch (err) {
        // 如果函式執行出錯,新的Promise物件的狀態為失敗
        onRejectedNext(err)
      }
    }
    
    
   // 執行對應函式
    switch (_status) {
      // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行
      case PENDING:
        this._fulfilledQueues.push(fulfilled)
        this._rejectedQueues.push(rejected)
        break
      // 當狀態已經改變時,立即執行對應的回撥函式
      case FULFILLED:
        fulfilled(_value)
        break
      case REJECTED:
        rejected(_value)
        break
    }
  })
}

完全版--1

ES6語法實現Promise
  // 判斷變數否為function
  const isFunction = variable => typeof variable === 'function'
  // 定義Promise的三種狀態常量
  const PENDING = 'PENDING'
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'

  class MyPromise {
    constructor (handle) {
      if (!isFunction(handle)) {
        throw new Error('MyPromise must accept a function as a parameter')
      }
      // 新增狀態
      this._status = PENDING
      // 新增狀態
      this._value = undefined
      // 新增成功回撥函式佇列
      this._fulfilledQueues = []
      // 新增失敗回撥函式佇列
      this._rejectedQueues = []
      // 執行handle
      try {
          //class是嚴格模式,如果不bind會Uncaught TypeError: Cannot read property  undefined
        handle(this._resolve.bind(this), this._reject.bind(this)) 
      } catch (err) {
        this._reject(err)
      }
    }
    // 新增resovle時執行的函式
    _resolve (val) {
      const run = () => {
          //不是進行狀態,已凝固直接返回
        if (this._status !== PENDING) return
        // 依次執行成功佇列中的函式,並清空佇列
        const runFulfilled = (value) => {
          let cb;
          while (cb = this._fulfilledQueues.shift()) {
            cb(value)
          }
        }
        // 依次執行失敗佇列中的函式,並清空佇列
        const runRejected = (error) => {
          let cb;
          while (cb = this._rejectedQueues.shift()) {
            cb(error)
          }
        }
        /* 如果resolve的引數為Promise物件,則必須等待該Promise物件狀態改變後,
          當前Promsie的狀態才會改變,且狀態取決於引數Promsie物件的狀態
        */
        if (val instanceof MyPromise) {
          val.then(value => {
            this._value = value
            this._status = FULFILLED
            runFulfilled(value)
          }, err => {
            this._value = err
            this._status = REJECTED
            runRejected(err)
          })
        } else {
          this._value = val
          this._status = FULFILLED
          runFulfilled(val)
        }
      }
      // 為了支援同步的Promise,這裡採用非同步呼叫
      setTimeout(run, 0)
    }
    // 新增reject時執行的函式
    _reject (err) { 
      if (this._status !== PENDING) return
      // 依次執行失敗佇列中的函式,並清空佇列
      const run = () => {
        this._status = REJECTED
        this._value = err
        let cb;
          //將刪除的第一個元素的值賦值給cb
        while (cb = this._rejectedQueues.shift()) {
          cb(err)
        }
      }
      // 為了支援同步的Promise,這裡採用非同步呼叫
      setTimeout(run, 0)
    }
    // 新增then方法
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      // 返回一個新的Promise物件
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
        // 封裝一個成功時執行的函式
        let fulfilled = value => {
          try {
            if (!isFunction(onFulfilled)) {
              onFulfilledNext(value)
            } else {
              let res =  onFulfilled(value);
              if (res instanceof MyPromise) {
                // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥
                res.then(onFulfilledNext, onRejectedNext)
              } else {
                //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式
                onFulfilledNext(res)
              }
            }
          } catch (err) {
            // 如果函式執行出錯,新的Promise物件的狀態為失敗
            onRejectedNext(err)
          }
        }
        // 封裝一個失敗時執行的函式
        let rejected = error => {
          try {
            if (!isFunction(onRejected)) {
              onRejectedNext(error)
            } else {
                let res = onRejected(error);
                if (res instanceof MyPromise) {
                  // 如果當前回撥函式返回MyPromise物件,必須等待其狀態改變後在執行下一個回撥
                  res.then(onFulfilledNext, onRejectedNext)
                } else {
                  //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式
                  onFulfilledNext(res)
                }
            }
          } catch (err) {
            // 如果函式執行出錯,新的Promise物件的狀態為失敗
            onRejectedNext(err)
          }
        }
        switch (_status) {
          // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行
          case PENDING:
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
          // 當狀態已經改變時,立即執行對應的回撥函式
          case FULFILLED:
            fulfilled(_value)
            break
          case REJECTED:
            rejected(_value)
            break
        }
      })
    }
    // 新增catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    // 新增靜態resolve方法
    static resolve (value) {
      // 如果引數是MyPromise例項,直接返回這個例項
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
    // 新增靜態reject方法
    static reject (value) {
      return new MyPromise((resolve ,reject) => reject(value))
    }
    // 新增靜態all方法
    static all (list) {
      return new MyPromise((resolve, reject) => {
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 陣列引數如果不是MyPromise例項,先呼叫MyPromise.resolve
          this.resolve(p).then(res => {
            values[i] = res
            count++
            // 所有狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一個被rejected時返回的MyPromise狀態就變成rejected
            reject(err)
          })
        }
      })
    }
    // 新增靜態race方法
    static race (list) {
      return new MyPromise((resolve, reject) => {
        for (let p of list) {
          // 只要有一個例項率先改變狀態,新的MyPromise的狀態就跟著改變
          this.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    finally (cb) {
      return this.then(
        value  => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => { throw reason })
      );
    }
  }

註釋:

1.Try

2.consturctor

完全版-2

ES5語法實現
(function () {
    // 判斷function
    function isFunction(fn) {
        return typeof fn === 'function';
    }

    // 狀態 pending、fulfilled、rejected
    var PENDING = 'pending';
    var FULFILLED = 'fulfilled';
    var REJECTED = 'rejected';

    // 構造方法
    var Kromise = function (handle) {
        // 當前狀態
        this._status = PENDING;
        // 新增成功回撥佇列
        this._fulfilledQueue = [];
        // 新增失敗回撥佇列
        this._rejectedQueue = [];
        // 引用當前this物件
        var self = this;

        if (!isFunction(handle)) {
            throw new Error('Parameter handle is not a function!')
        }

        // 新增resolve時執行的函式
        function _resolve(val) {
            var run = function () {
                if (self._status !== PENDING) return;
                // 依次執行成功佇列中的函式,並清空佇列
                var runFulfilled = function (res) {
                    var resolve;
                    while (resolve = self._fulfilledQueue.shift()) { // 出棧
                        resolve(res);
                    }
                };

                // 依次執行失敗佇列中的函式,並清空佇列
                var runRejected = function (err) {
                    var reject;
                    while (reject = self._rejectedQueue.shift()) { // 出棧
                        reject(err);
                    }
                };
                /* 如果resolve的引數為Kromise物件,則必須等待該Kromise物件狀態改變後,
                 * 當前Kromise的狀態才會改變,且狀態取決於引數Kromise物件的狀態
                 */
                if (val instanceof Kromise) {
                    val.then(function (value) {
                        self._status = FULFILLED;
                        self._value = value;
                        runFulfilled(value)
                    }, function (err) {
                        self._status = REJECTED;
                        self._value = err;
                        runRejected(err);
                    })
                } else {
                    self._status = FULFILLED;
                    self._value = val;
                    runFulfilled(val);
                }

            };
            // 為了支援同步的Promise,這裡採用非同步呼叫
            setTimeout(run, 0)
        }

        // 新增reject時執行的函式
        function _reject(err) {
            var run = function () {
                if (self._status !== PENDING) return;
                // 依次執行成功佇列中的函式,並清空佇列
                self._status = REJECTED;
                self._value = err;
                var reject;
                while (reject = self._fulfilledQueue.shift()) { // 出棧
                    reject(err);
                }
            };
            // 為了支援同步的Promise,這裡採用非同步呼叫
            setTimeout(run, 0)
        }

        // 執行handle,捕獲異常
        try {
            handle(_resolve.bind(this), _reject.bind(this));
        } catch (e) {
            _reject(e);
        }
    };

    // 屬性
    Kromise.length = 1;

    // 例項方法
    // 實現then方法
    Kromise.prototype.then = function (onFulfilled, onRejected) {
        var self = this;
        // 返回一個新的Kromise物件
        return new Kromise(function (onFulfilledNext, onRejectedNext) {
            // 成功時的回撥
            var fulfilled = function (val) {
                try {
                    // 如果不是函式,值穿透
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(val)
                    } else {
                        var res = onFulfilled(val);
                        // 如果當前回撥函式返回Kromise物件,必須等待其狀態改變後在執行下一個回撥
                        if (res instanceof Kromise) {
                            res.then(onFulfilledNext, onRejectedNext);
                        } else {
                            //否則會將返回結果直接作為引數,傳入下一個then的回撥函式,並立即執行下一個then的回撥函式
                            onFulfilledNext(res);
                        }
                    }
                } catch (e) {
                    // 如果函式執行出錯,新的Kromise物件的狀態為失敗
                    onRejectedNext(e);
                }
            };
            // 失敗時的回撥
            var rejected = function (err) {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(err)
                    } else {
                        var res = onRejected(err);
                        if (res instanceof Kromise) {
                            res.then(onFulfilledNext, onRejectedNext);
                        } else {
                            onFulfilledNext(res);
                        }
                    }
                } catch (e) {
                    onRejectedNext(e)
                }
            };

            switch (self._status) {
                // 當狀態為pending時,將then方法回撥函式加入執行佇列等待執行
                case PENDING:
                    self._fulfilledQueue.push(fulfilled);
                    self._rejectedQueue.push(rejected);
                    break;
                // 當狀態已經改變時,立即執行對應的回撥函式
                case FULFILLED:
                    fulfilled(self._value);
                    break;
                case REJECTED:
                    rejected(self._value);
                    break;
            }
        });
    };

    // 實現catch方法
    Kromise.prototype.catch = function (onRejected) {
        return this.then(undefined, onRejected);
    };

    // 實現finally方法
    Kromise.prototype.finally = function (onFinally) {
        return this.then(function (value) {
            Kromise.resolve(onFinally()).then(function () {
                return value;
            })
        }, function (err) {
            Kromise.resolve(onFinally()).then(function () {
                throw new Error(err);
            })
        })
    };

    // 靜態方法
    // 實現resolve方法
    Kromise.resolve = function (value) {
        // 如果引數是Kromise例項,直接返回這個例項
        if (value instanceof Kromise) {
            return value;
        }
        return new Kromise(function (resolve) {
            resolve(value)
        })
    };
    // 實現reject方法
    Kromise.reject = function (value) {
        return new Kromise(function (resolve, reject) {
            reject(value)
        })
    };
    // 實現all方法
    Kromise.all = function (arr) {
        var self = this;
        return new Kromise(function (resolve, reject) {
            var values = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                // 陣列引數如果不是Kromise例項,先呼叫Kromise.resolve
                self.resolve(arr[i]).then(function (res) {
                    values.push(res);
                    // 所有狀態都變成fulfilled時返回的Kromise狀態就變成fulfilled
                    if (values.length === arr.length) {
                        resolve(values);
                    }
                }, function (e) {
                    // 有一個被rejected時返回的Kromise狀態就變成rejected
                    reject(e);
                })
            }
        })
    };

    // 實現race方法
    Kromise.race = function (arr) {
        var self = this;
        return new Kromise(function (resolve, reject) {
            for (var i = 0, len = arr.length; i < len; i++) {
                // 只要有一個例項率先改變狀態,新的Kromise的狀態就跟著改變
                self.resolve(arr[i]).then(function (res) {
                    resolve(res);
                }, function (err) {
                    reject(err);
                })
            }
        })
    };
    // 實現any方法
    Kromise.any = function (arr) {
        var self = this;
        return new Kromise(function (resolve, reject) {
            var count = 0;
            var errors = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                // 只要有一個例項狀態變為fulfilled,新的Kromise狀態就會改變為fulfilled
                self.resolve(arr[i]).then(function (res) {
                    resolve(res);
                }, function (err) {
                    errors[count] = err;
                    count++;
                    // 否則等待所有的rejected,新的Kromise狀態才會改變為rejected
                    if (count === arr.length) {
                        reject(errors);
                    }
                })
            }
        })

    };
    // 實現allSettled方法
    Kromise.allSettled = function (arr) {
        var results = [];
        var len = arr.length;
        for (var i = 0; i < len; i++) {
            this.resolve(arr[i]).then(function (res) {
                results.push({status: FULFILLED, value: res});
            }, function (err) {
                results.push({status: REJECTED, value: err});
            })
        }
        // 一旦結束,狀態總是`fulfilled`,不會變成`rejected`
        return new Kromise(function (resolve, reject) {
            resolve(results)
        })
    };
    // 實現try方法
    Kromise.try = function (fn) {
        if (!isFunction(fn)) return;
        return new Kromise(function (resolve, reject) {
            return resolve(fn());
        })
    };

    // 掛載
    window.Kromise = Kromise;
})();

總結

promise通過自己的回撥巢狀解決別人的問題

8.參考文件

[1].JavaScript | MDN

[2].Promise迷你書

[3].廖雪峰的官方網站

[4].ES6網道教程

相關文章