Javascript 異步編程(五)Promise

Javascript 異步編程(五)

Promise

Promise 承諾,在異步編程中用來描述異步任務,當前不執行,承諾執行,美劇裏常常是I
promise~ 掛在嘴邊javascript

ES6 Promise Api

  • new Promise(..) 構造器前端

    var p = new Promise( function(resolve,reject){
    // 接收函數做爲構造器參數   
    // resolve(..)用於將promise狀態從pending改成fulfilled
    // reject(..)用於將promise狀態從pending改成rejected
    } );

    resolve(..) 既可能完成 promise,也可能拒絕,要根據傳入參數而定。java

    若是傳給 resolve(..) 的是一個非 Promise、非 thenable 的當即值,這個 promise 就會用這個值完成。git

若是傳給 resolve(..) 的是一個真正的 Promise 或 thenable 值,這個值就會被遞歸展開,而且(要構造的)promise 將取用其最終決議值或狀態。es6

  • Promise.resolve 建立一個已完成的 Promisegithub

    語法上二者是等同的面試

    var p1 = new Promise( function(resolve,reject){
     resolve( "Oops" );
    } );
    var p2 = Promise.resolve( "Oops" );
  • Promise.reject 建立一個已被拒絕的 Promise編程

    var p1 = new Promise( function(resolve,reject){
     reject( "Oops" );
    } );
    var p2 = Promise.reject( "Oops" );
  • Promise.all

    輸入一組promise,返回一個新的promise。只有傳入的所有 promise 的狀態都變爲fulfilled,返回 promise 才能改變爲fulfilled。能夠理解爲邏輯與(&&數組

  • Promise.allSettled

    輸入一組promise,返回一個新的promise。只有傳入的全部 promise 的狀態都改變後(fulfilledrejected),返回 promise 才能改變爲fulfilledpromise

  • Promise.race

    輸入一組promise,返回一個新的promise。結果的promise與第一個 狀態發生改變(fulfilledrejected)promise相同

  • Promise.any

    接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。相似於邏輯或 (||)

若向 Promise.all([ .. ]) 傳入空數組,它會當即完成,但 Promise.race([ .. ]) 會掛住,且永遠不會完成。

實例方法:

  • then()

    then(..) 接受一個或兩個參數:第一個用於完成回調,第二個用於拒絕回調。若是二者中
    的任何一個被省略或者做爲非函數值傳入的話,就會替換爲相應的默認回調。默認完成回
    調只是把消息傳遞下去,而默認拒絕回調則只是從新拋出(傳播)其接收到的出錯緣由

  • catch()

    只接受一個拒絕回調做爲參數,並自動替換默認完成回調。換句話說,它等價於 then(null,..):

  • finally()

    接收一個參數,不論promise的最終狀態如何都會執行,

then(..) 和 catch(..) 也會建立並返回一個新的 promise,這個 promise 能夠用於實現
Promise 鏈式流程控制。

若是完成或拒絕回調中拋出異常,返回的 promise 是被拒絕的。

若是任意一個回調返回非 Promise、非 thenable 的當即值,這個值會被用做返回 promise 的完成值。

若是完成處理函數返回一個 promise 或 thenable,那麼這個值會被展開,並做爲返回promise 的決議值。

異常控制

對於錯誤處理,通常咱們使用try…catch,可是它只能捕捉同步代碼的異常。

try{
  setTimeout(function () {
    console.log('happen',x)
  })
}catch (e) {
  console.log('exception',e)
}
// Uncaught ReferenceError: x is not defined

在promise中,若是發生異常,將由then()中的reject處理函數進行錯誤捕獲。

Promise.reject(10).then((val)=>{
  console.log(val)
},(err)=>{
  console.log('then',err)
})
// then 10
Promise.resolve(10).then(function (val) {
  console.log(val)
  return Promise.reject(val)
}).then(val => {
  console.log(val)
}, err => {
  console.log('then', err)
})
// 10
// then 10

若是忘記在then()中對異常進行捕獲,將會丟失被忽略和拋棄的Promise錯誤。因而,實現Promise異常控制的最佳實踐就是以catch()結尾來進行異常處理。

Promise.resolve(10).then(function (val) {
  console.log(val)
  return Promise.reject(val)
}).then(val => {
  console.log(val)
}).catch(err=>{
  console.log('err',err)
})
// err 10

可是,若是咱們在catch中發生了異常呢?

Promise.reject(10).catch(err=>{
  console.log('catch',10+x)
})
// ReferenceError: x is not defined
Promise.reject(10).catch(err => {
  console.log('catch', 10 + x)
}).catch(err=>{
  console.log('catch2',err)
})
// catch2 ReferenceError: x is not defined

其實咱們能夠在調用鏈後面再次補充一個catch來保證捕獲任何可能出現的錯誤。可是這樣的寫法不是很優雅,有種經常使用的方法是使用done(標誌着promise的結尾,不會建立和返回promise,不在es6規範中),現階段能夠經過增長 polyfill

Promise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected)
.catch(function (err) {
    // 拋出一個全局錯誤
    setTimeout(() => { throw err }, 0);
});
};

在瀏覽器中,咱們可使用 unhandledrejection 事件來捕獲這類 error:

window.addEventListener('unhandledrejection', function(event) {
  // 這個事件對象有兩個特殊的屬性:
  alert(event.promise); // [object Promise] - 生成該全局 error 的 promise
  alert(event.reason); // Error: Whoops! - 未處理的 error 對象
});
new Promise(function() {
  throw new Error("Whoops!");
});

若是出現了一個 error,而且在這兒沒有 .catch,那麼 unhandledrejection 處理程序(handler)就會被觸發,並獲取具備 error 相關信息的 event 對象,因此咱們就能作一些後續處理了

Promise 實踐

如下代碼的執行順序是?

const promise=new Promise((resolve,reject)=>{
    console.log(1);
    resolve();
    console.log(2);
})
promise.then(()=>{
    console.log(3);
})
console.log(4);
// 1
// 2
// 4
// 3
  1. 構造器new Promise是同步執行的,因此先打印1,2
  2. then的回調屬於微任務,異步執行
  3. 同步執行console.log(4),接着打印 4
  4. 開始執行微任務隊列,console.log(3),打印出3

第二段代碼

const promise=Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);//1

過程分解

  1. Promise.resolve(1) 生成的promise ,返回值爲1,狀態爲fulfilled
  2. then(2)中,傳入了非函數,返回的promise 的值爲上一步的promise的值(即爲1),狀態爲fulfilled
  3. then(Promise.resolve(3)) 這一步中參數類型爲promise,仍取上一步的promise的值(即爲1),狀態爲fulfilled
  4. then(console.log), 這一步參數爲函數,傳入值1

最佳實踐

  1. 不要忘記catch捕獲
  2. then方法中使用return
  3. 傳遞函數給then方法
  4. 不要把promise寫成嵌套

手動實現Promise

class Promise {
  constructor (executor) {
    // 參數校檢
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`)
    }
    this.initValue()
    this.initBind()
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }

  }

  initValue () {
    // state:pending/fulfilled/rejected
    this.value = null//終值
    this.reason = null//拒因
    this.state = Promise.PENDING
    //用於處理異步
    this.onFulfilledCallbacks = [] //成功回調
    this.onRejectedCallbacks = []//失敗回調
  }

  resolve (value) {
    //成功後的一系列操做(狀態的改變,成功回調的執行)
    //狀態不可逆 從pending開始
    if (this.state === Promise.PENDING) {
      this.state = Promise.FULFILLED
      this.value = value
      this.onFulfilledCallbacks.forEach(fn => fn(this.value))
    }
  }

  //綁定this
  initBind () {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }

  reject (reason) {
    //失敗後的一系列操做(狀態的改變,失敗回調的執行)
    if (this.state === Promise.PENDING) {
      this.state = Promise.REJECTED
      this.reason = reason
      this.onRejectedCallbacks.forEach(fn => fn(this.reason))
    }
  }

  catch (onRejected) {
    return this.then(null, onRejected) // 至關於then裏的成功回調只傳個null
  }

  then (onFulfilled, onRejected) {
    //實現鏈式調用且改變了後面then方法的值,必須經過新的實例
    let promise2 = new Promise((resolve, reject) => {
      // 參數校檢
      if (typeof onFulfilled !== 'function') {
        onFulfilled = function (value) {
          return value
        }
      }
      if (typeof onRejected !== 'function') {
        onRejected = function (reason) {
          throw reason
        }
      }

      if (this.state === Promise.FULFILLED) {
        //模擬異步任務
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value)
            Promise.resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

      }
      if (this.state === Promise.REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason)
            Promise.resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

      }
      // 處理異步
      if (this.state === Promise.PENDING) {
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => {
            try {
              const x = onFulfilled(value)
              Promise.resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        this.onRejectedCallbacks.push(reason => {
          setTimeout(() => {
            try {
              const x = onRejected(reason)
              Promise.resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    })
    return promise2
  }
}

Promise.resolvePromise = function (promise, x, resolve, reject) {
  // x與promise相等時 循環引用
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  // 函數調用一次的開關項 防止重複調用
  let called
  if (x instanceof Promise) {
    /**
     * x爲 Promise
     * 1. x 爲 pending,promise需保持 pending 直至x被拒絕或執行
     * 2. x 爲 fulfilled 用相同的值執行promise
     * 3. x 爲 rejected 用相同的拒因執行promise
     */
    x.then(value => {Promise.resolvePromise(promise, value, resolve, reject)},
      reason => {reject(reason)})
  } else if (x && (typeof x === 'object' || typeof x === 'function')) {
    // x 爲對象或函數
    try {
      let then = x.then
      // 處理thenable
      if (typeof then === 'function') {
        then.call(x,
          value => {
            if (called) return
            called = true
            Promise.resolvePromise(promise, value, resolve, reject)
          },
          reason => {
            if (called) return
            called = true
            reject(reason)
          })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}
Promise.resolve = function (val) {
  return new Promise(resolve => {
    resolve(val)
  })
}
Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason)
  })
}
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(data => {
        resolve(data)
      })
    }
  })
}
Promise.all = function (promises) {
  let result = []
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(value => {
        result.push(value)
        if (result.length === promises.length) {
          resolve(arr)
        }
      }, reject)
    }
  })
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
module.exports = Promise

Reference

Promises/A+ 規範

中文翻譯

BAT前端經典面試問題:史上最最最詳細的手寫Promise教程

相關文章
相關標籤/搜索