深入淺出Promise


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網道教程

Tags: