Promise
是ES6
加入標準的一種異步編程解決方案,一般用來表示一個異步操做的最終完成 (或失敗)。Promise
標準的提出,解決了JavaScript
地獄回調的問題。html
var p = new Promise( function(resolve, reject) {...} /* executor */ );
p.then(() => {}) // 成功resolve .catch(()=> {}); // 失敗reject 複製代碼
Promise
構造函數executor
函數,resolve
和reject
兩個函數做爲參數傳遞給executor
(executor 函數在Promise
構造函數返回所建 promise 實例對象前被調用)。resolve
和reject
函數被調用時,分別將promise
的狀態改成fulfilled
(完成)或rejected
(失敗)。executor
內部一般會執行一些異步操做,一旦異步操做執行完畢(可能成功/失敗),要麼調用resolve
函數來將promise
狀態改爲fulfilled
,要麼調用reject
函數將promise
的狀態改成rejected
。若是在executor
函數中拋出一個錯誤,那麼該promise
狀態爲rejected
。executor
函數的返回值將被忽略。前端
Promise
對象在建立以後,並不必定會立刻就有值,只是一個代理結果,它存在三種狀態:html5
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)) 複製代碼
上面的代碼和執行結果簡單的說明了Promise
建立,存在狀態和方法的使用。下面再詳細介紹Promise
的使用方法。數組
本文一開始就提出,Promise
能解決前端臭名昭著的地獄回調問題,那什麼是地獄回調呢?這裏簡單拿一個場景和實現方法來解釋一下:promise
前端很常見是下面一個場景,咱們須要實現一個用戶修改頭像的功能。首先咱們須要將一張圖片壓縮並提交給後端,後端返回該圖片保存的 url,前端拿保存的 url 和用戶 id 提交給服務器來修改用戶頭像。
大概代碼實現:
// 讀取頭像,異步過程,成功以後執行回調函數
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
一個異常,就能捕獲整個過程的異常。
變量的的方式建立:
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.resolve
和reduce
函數結合來實現:
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
的加持下,咱們無需擔憂。
Promise
是
ES6
加入標準的一種異步編程解決方案,一般用來表示一個異步操做的最終完成 (或失敗)。
promise
鏈式調用的特色,解決了
js
地獄回調的問題。
promise
提供了一系列屬性和方法。合理利用
Promise.all
和
Promise.resolve
能夠解決很多問題。
下期會結合本期的講解,配合一些經典的promise
面試題來更深刻的瞭解promise
。畢竟promise
過重要,太好用了。 後面會有async/await
和promise
的講解。
參考資料:
promise 使用指南[1]
相關閱讀:
知道html5 Web Worker標準嗎?能實現JavaScript的多線程?
學習如逆水行舟,不進則退,前端技術飛速發展,若是天天不堅持學習,就會跟不上,我會陪着你們,天天堅持推送博文,跟你們一同進步,但願你們能關注我,第一時間收到最新文章。
公衆號前端每日面試題分享:
promise使用指南: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises