promise講解

promise 簡介

PromiseES6加入標準的一種異步編程解決方案,一般用來表示一個異步操做的最終完成 (或失敗)。Promise標準的提出,解決了JavaScript地獄回調的問題。html

語法

var p = new Promise( function(resolve, reject) {...} /* executor */  );
p.then(() => {}) // 成功resolve .catch(()=> {}); // 失敗reject 複製代碼

構造函數

Promise構造函數executor 函數, resolvereject 兩個函數做爲參數傳遞給executor(executor 函數在Promise構造函數返回所建 promise 實例對象前被調用)。resolvereject 函數被調用時,分別將promise的狀態改成fulfilled(完成)或rejected(失敗)。executor 內部一般會執行一些異步操做,一旦異步操做執行完畢(可能成功/失敗),要麼調用resolve函數來將promise狀態改爲fulfilled,要麼調用reject 函數將promise的狀態改成rejected。若是在executor函數中拋出一個錯誤,那麼該promise 狀態爲rejectedexecutor函數的返回值將被忽略。前端

描述

Promise對象在建立以後,並不必定會立刻就有值,只是一個代理結果,它存在三種狀態:html5

  • pending: 初始狀態(等待狀態),既不是成功,也不是失敗狀態。
  • fulfilled: 意味着操做成功完成。
  • rejected: 意味着操做失敗完成。

方法

Promise.all(iterable)

這個方法返回一個新的 promise 對象。通常該方法會接受一個iterable參數,裏面是一個promise列表,當全部的promise都觸發成功時纔會觸發成功,一旦有一個失敗了,則會立刻中止其餘promise的執行。當iterable裏面的結果都執行成功了,這個新的promise對象會將全部的結果已數組的形式依次返回。當有一個失敗是,這個新的promise對象會將失敗的信息返回。web

Promise.race(iterable)

當 iterable 參數裏的任意一個子 promise 被成功或失敗後,父 promise 立刻也會用子 promise 的成功返回值或失敗詳情做爲參數調用父 promise 綁定的相應句柄,並返回該 promise 對象。面試

Promise.reject(rease)

返回一個狀態爲失敗的 Promise 對象,並將給定的失敗信息傳遞給對應的處理方法ajax

Promise.resolve(value)

返回一個狀態由給定 value 決定的 Promise 對象。若是該值是 thenable(即,帶有 then 方法的對象),返回的 Promise 對象的最終狀態由 then 方法執行決定;不然的話(該 value 爲空,基本類型或者不帶 then 方法的對象),返回的 Promise 對象狀態爲 fulfilled,而且將該 value 傳遞給對應的 then 方法。一般而言,若是你不知道一個值是不是 Promise 對象,使用 Promise.resolve(value) 來返回一個 Promise 對象,這樣就能將該 value 以 Promise 對象形式使用。編程

實例

枯燥的說明終於結束了,下面用一個具體的實例來展現各類狀態和方法:後端

// 建立promise1,構造函數有2個參數resolve,reject
const p1 = new Promise((resolve, reject) => {  console.log('我一開始就會執行!')  setTimeout(() => {  resolve('timeout 1')  }, 1000) }) // promise2 const p2 = new Promise((resolve, reject) => {  setTimeout(() => {  resolve('timeout 2')  }, 2000) }) console.log(p1, p2, 'p1, p2狀態') // Promise.race方法,任何一個成功或失敗就會執行then方法 Promise.race([p1, p2]).then(res => {  console.log(res, 'race結果') }) // Promise.all方法 Promise.all([p1, p2]).then(res => {  console.log(res, 'all結果')  console.log('此時:', p1, p2, 'p1, p2狀態') }) // Promise.reject方法測試 Promise.reject('失敗').catch(err => console.log('Promise.reject:' + err)) // Promise.resolve方法測試 Promise.resolve(1).then(r => console.log('Promise.resolve的結果1:' + r)) Promise.resolve(p1).then(r => console.log('Promise.resolve的結果2:' + r)) 複製代碼

1.png 上面的代碼和執行結果簡單的說明了Promise建立,存在狀態和方法的使用。下面再詳細介紹Promise的使用方法。數組

promise 的使用方法

本文一開始就提出,Promise能解決前端臭名昭著的地獄回調問題,那什麼是地獄回調呢?這裏簡單拿一個場景和實現方法來解釋一下:promise

場景

前端很常見是下面一個場景,咱們須要實現一個用戶修改頭像的功能。首先咱們須要將一張圖片壓縮並提交給後端,後端返回該圖片保存的 url,前端拿保存的 url 和用戶 id 提交給服務器來修改用戶頭像。

  1. 異步一:加載圖片
  2. 異步二:壓縮圖片
  3. 異步三:上傳圖片
  4. 異步四:提交保存

大概代碼實現:

// 讀取頭像,異步過程,成功以後執行回調函數
function readImg(callback) {  const img = new Image();  img.onload = function () {  callback && callback(img)  } }  // 壓縮一張圖片,壓縮過程異步,成功以後執行回調函數 function compression(img, callback) {  zipImg(img, callback) } // 上次用戶頭像,異步請求 function uploadImg(img, callback) {  $.ajax({  url: '/upload',  data: img,  success: function (url) {  callback && callback(url)  }  }) } // 保存最終數據,用戶頭像,異步請求 function saveData(url, callback) {  $.ajax({  url: '/save',  data: {id, url},  success: function (res) {  callback && callback(res)  }  }) } // 保存函數 function submit() {  readImg(function (img) {  compression(img, function (img) {  uploadImg(img, function (url) {  saveData(url, function (res) {  console.log(res)  })  })  })  }) } 複製代碼

上面的場景在咱們開發的過程當中很常見,由於js的異步特性,異步執行結果只能在回調函數裏面才能拿到,因此就有了最終的回調地獄。這個過程異常極難排查,並且一層一層嵌套,代碼極難維護也不美觀。利用promise鏈式調用chaining)的特色,咱們能夠將上面的代碼改爲下面的形式:

function submit() {
 readImg().then(img => {  return compression(img)  }).then(img => {  return uploadImg(img)  }).then(url => {  return saveData(url)  }).then(res => {  console.log(res)  }).catch(err => {  console.log(err)  }) } 複製代碼

能夠看到代碼結構不只變成平鋪的了,異常處理也變得簡單了,只須要最後catch一個異常,就能捕獲整個過程的異常。

用法

promise 建立

變量的的方式建立:

const myFirstPromise = new Promise((resolve, reject) => {
 // ?作一些異步操做,最終會調用下面二者之一:  // resolve(someValue); // fulfilled  // ?或  // reject("failure reason"); // rejected }); myFirstPromise.then(res =>{  // 成功doSomething } ).catch(err => {  // 失敗doSomething }) 複製代碼

函數的方式建立:

const myFirstPromise = function() {
 return new Promise((resolve, reject) => {  // 一個異步過程  // resolve() 成功 fulfilled  // reject() 失敗 rejected  }) } myFirstPromise().then(res =>{  // 成功doSomething } ).catch(err => {  // 失敗doSomething }) 複製代碼

使用場景

在異步時,給出異步後的結果:

const myFirstPromise = function() {
 return new Promise((resolve, reject) => {  setTimeout(() => {  resolve()  },1000)  }) } myFirstPromise.then(()=>{  console.log('一秒後!') }) 複製代碼

多個異步都須要結果才往下執行時,使用promise.all:

Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ }); 複製代碼

順序執行一組異步,拿到最後的結果,使用Promise.resolvereduce函數結合來實現:

var funca = function (a) {
 return new Promise((resolve, reject) => {  setTimeout(() => {  console.log(a, 'result funca');  resolve(a + 1)  }, 1000)  }) } var funcb = function (result) {  return new Promise((resolve, reject) => {  setTimeout(() => {  console.log(result, 'result funcb')  resolve(2 + result)  }, 1000)  }) }  var funcc = function(result) {  return new Promise((resolve, reject) => {  setTimeout(() => {  console.log(result, 'result funcc')  resolve(3 + result)  }, 1000)  }) } var funcd = function(d) {  console.log(d, 'result funcd')  return d + 4 } const applyAsync = (acc,val) => acc.then(val); const composeAsync = (...dd) => x => dd.reduce(applyAsync, Promise.resolve(x)); const transformData = composeAsync(funca, funcb, funcc, funcd); transformData(1).then(result => console.log(result,'last result')).catch(e => console.log(e)); 複製代碼

執行結果 上面 3 個異步函數和一個同步函數,咱們利用Promise.resolve的特性,順序執行了 3 個異步函數,最後在then裏面取到了最後的執行結果。並且上面的函數是可複用的,能順序執行任意數量的異步函數和同步函數。

常見錯誤寫法

在編寫 Promise 鏈時,須要注意如下示例中展現的幾個錯誤:

// 錯誤示例,包含 3 個問題!
doSomething().then(function(result) {  doSomethingElse(result) // 沒有返回 Promise 以及沒有必要的嵌套 Promise  .then(newResult => doThirdThing(newResult)); }).then(() => doFourthThing()); // 最後,是沒有使用 catch 終止 Promise 調用鏈,可能致使沒有捕獲的異常 複製代碼

第一個錯誤是沒有正確地將事物相鏈接。當咱們建立新 Promise 但忘記返回它時,會發生這種狀況。所以,鏈條被打破,或者更確切地說,咱們有兩個獨立的鏈條競爭(同時在執行兩個異步而非一個一個的執行)。這意味着 doFourthThing() 不會等待 doSomethingElse()doThirdThing() 完成,而且將與它們並行運行,多是無心的。單獨的鏈也有單獨的錯誤處理,致使未捕獲的錯誤。

第二個錯誤是沒必要要地嵌套,實現第一個錯誤。嵌套還限制了內部錯誤處理程序的範圍,若是是非預期的,可能會致使未捕獲的錯誤。其中一個變體是 Promise 構造函數反模式,它結合了 Promise 構造函數的多餘使用和嵌套。

第三個錯誤是忘記用 catch 終止鏈。這致使在大多數瀏覽器中不能終止的 Promise 鏈裏的 rejection。

一個好的經驗法則是老是返回或終止 Promise 鏈,而且一旦你獲得一個新的 Promise,返回它。下面是修改後的平面化的代碼:

doSomething()
.then(function(result) {  return doSomethingElse(result); }) .then(newResult => doThirdThing(newResult)) .then(() => doFourthThing()) .catch(error => console.log(error)); 複製代碼

兼容性

兼容性 大多數瀏覽器已經支持了promise,在babel的加持下,咱們無需擔憂。

總結

  1. PromiseES6加入標準的一種異步編程解決方案,一般用來表示一個異步操做的最終完成 (或失敗)。
  2. promise鏈式調用的特色,解決了 js 地獄回調的問題。
  3. promise提供了一系列屬性和方法。合理利用 Promise.allPromise.resolve能夠解決很多問題。

寫在最後

下期會結合本期的講解,配合一些經典的promise面試題來更深刻的瞭解promise。畢竟promise過重要,太好用了。 後面會有async/awaitpromise的講解。

參考資料:

promise 使用指南[1]

相關閱讀:

前端異步是什麼?哪些狀況下會發生異步?

知道html5 Web Worker標準嗎?能實現JavaScript的多線程?

學習如逆水行舟,不進則退,前端技術飛速發展,若是天天不堅持學習,就會跟不上,我會陪着你們,天天堅持推送博文,跟你們一同進步,但願你們能關注我,第一時間收到最新文章。

公衆號前端每日面試題分享: 公衆號

參考資料

[1]

promise使用指南: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

相關文章
相關標籤/搜索