什麼是promise?
Promise(承諾),在程序中的意思就是承諾我過一段時間(通常是一個異步操作)後會給你一個結果,是異步編程的一種解決方案。從語法上說,原生Promise 是一個對象,從它可以獲取異步操作的消息。
promise的特點
- 對象的狀態不受外界影響。
promise有三種狀態 pending
(進行中) fulfilled
(已成功) rejected
(已失敗),只有異步操作的結果,纔可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
- 一旦從等待狀態變成爲其他狀態就永遠不能更改狀態了。
promise只有兩種狀態改變:pending
(進行中)--> fulfilled
(已成功) ;pending
(進行中)--> rejected
(已失敗)。
當狀態改變結束時稱爲resolve
(已固定),一旦狀態變爲 resolved 後,就不能再次改變爲Fulfilled
。
- 一旦新建Promise就會立即執行,無法中途取消。
- 如果不設置回調函數callback,Promise內部拋出的錯誤,就不會反應到外部。
- 當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
promise實例操作
首先創造了一個Promise實例
let promise=new Promsie(function(resolve,rejec){
if(/*異步執行成功*/){
resolve(value);
}else{
reject(error);
}
})
promise.then(function(){
//回調執行成功之後的操作
},function(){
//回調執行失敗之後的操作,可選
});
Promise構造函數接受一個函數作爲參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供。當異步操作成功時(pending--fulfilled
),調用resolve(value)
函數把操作結果當成參數傳出,當異步操作成功時(pending--rejected
)調用 reject(error)
函數把錯誤返回。Promise實例生成以後,用then
方法分別指定resolved狀態和rejected狀態的回調函數。
下面看一下構造函數原型的方法
-
Promise.prototype.then()
-
Promise.prototype.then()
作用是爲 Promise 實例添加狀態改變時的回調函數。接受兩個回調函數作爲參數。第一個回調函數是Promise對象的狀態變爲resolved時調用,第二個回調函數是Promise對象的狀態變爲rejected時調用。其中,第二個函數是可選的,不一定要提供。 - 而
Promise.prototype.then()
返回的是另一個Promise對象,後面還可以接着調用then方法。
-
-
Promise.prototype.catch()
-
Promise.prototype.catch()
則是.then(null, rejection)
或.then(undefined, rejection)
的別名,用於指定發生錯誤時的回調函數。 Promise 對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤總是會被下一個catch語句捕獲。 -
Promise.catch()
方法返回的也是一個 Promise 對象,因此後面還可以接着調用then方法。
-
上述代碼也可以理解成這樣:
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
console.log('發生錯誤!', error);
});
-
Promise.prototype.finally()
-
finally
方法用於指定不管 Promise 對象最後狀態如何,都會執行的回調函數。該方法是 ES2018 引入標準的。 -
finally
方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise 狀態到底是fulfilled還是rejected。這表明,finally
方法裏面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。 -
finally
本質上是then
方法的特例。promise.then(()=>{}).catch(()=>{}).finally(() => { // 操作 }); // 等同於 promise.then(result => { // 操作 return result; }).catch( error => { // 操作 throw error; });
-
-
promise的鏈式調用
- 由於
.then
每次調用返回的都是一個新的Promise實例,如果then中返回的是一個結果的話會把這個結果傳遞下一次then中的成功回調,所以可以鏈式調用該實例。 - 如果then中出現異常,會走下一個then的失敗回調,catch 會捕獲到沒有捕獲的異常。
-
在 then中使用了return,那麼 return 的值會被
Promise.resolve()
包裝,then中也可以不傳遞參數,如果不傳遞會透到下一個then中。Promise.resolve(1).then(res => { console.log(res) return 2 //包裝成 Promise.resolve(2) }).catch(err => 3).then().then(res => console.log(res))
- 由於
promise自身API
- Promise.resolve()
將現有的對象轉換(包裝)成 promise對象。
四種參數類型:
-
不帶參數傳遞 --- 返回一個新的狀態爲resolve的promise對象。
let p = Priomse.resolve() // p就是promise
- 參數是一個 Promise 實例--- 返回 當前的promise實例
-
參數是帶then方法的對象
let data = { then:function(resolve,reject){ resovle('帶then方法的對象') } } Promise.resolve(data).thne((res)=> console.log(res)) // '帶then方法的對象'
返回一個新的promise,並直接執行
then
的方法,promise對象的狀態就變爲resolved
,從而立即執行最後那個then方法指定的回調函數,輸出'帶then方法的對象'
。
-
參數是非空,非then方法的對象,非proimse的
let p = Promise.resolve('foo') // 等價於 let p = new Promise(resolve => resolve('foo')) p.then(res=>console.log(res)) //'foo'
返回一個新的狀態爲resolve的promise對象,所以
then
回調函數會立即執行。Promise.resolve
方法的參數,會同時傳給回調函數。 -
Promise.reject()
-
參數爲非then對象時-----
Promise.reject(reason)
方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
let p = Promise.reject('error') // 等價於 let p = new Primose((resolve,reject)=>reject('出錯了')}) //處理錯誤的回調 p.then((null,res)=>console.log(res)) //'出錯了'
-
參數是帶then方法的對象 ---返回的並不是
then
方法的回調函數,而是data對象本身let data = { then:function(resolve,reject){ reject('帶then方法的對象出錯了') } } Promise.resolve(data).thne((null,res)=> console.log(res)) // data //等同於 Promise.resolve(data).catch(res=> console.log(res)) // data
-
-
Promise.all()
該方法將多個promise實例,包裝成一個新的promise實例。let p = Promise.all([p1,p2,p3])
參數不一定爲數組,但必須爲一個可迭代
Iterator
,且返回的每個成員(p1,p2,p3)都是 Promise 實例,如果不是,就會先調用的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。var p = Promise.all([1,2,3]); var p2 = Promise.all([1,2,3, Promise.resolve(444)]); var p3 = Promise.all([1,2,3, Promise.reject(555)]); setTimeout(function() { console.log(p);// Promise { <state>: "fulfilled", <value>: Array[3] } console.log(p2); // Promise { <state>: "fulfilled", <value>: Array[4] } console.log(p3); // Promise { <state>: "rejected", <reason>: 555 } }); p.then(function (posts) { // ..當有返回值的時候纔會回調 }).catch(function(reason){ // ... });
- 當
p1
,p2
,p3
中得實例都改變成fulfilled
(已成功)時,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。 - 當
p1
,p2
,p3
中得實例其中一項的改變成rejected
(已失敗)時,p的狀態就變成rejected
,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。 -
Promise.all()是異步解析,只有這當所有實例的狀態都變成
fulfilled
,或者其中有一個變爲rejected
,纔會調用Promise.all方法後面的回調函數then
,catch
方法。但是當且僅當傳遞的iterable爲空時,Promise.all纔會同步解析。var p = Promise.all([]); console.log(p);//Promise { <state>: "fulfilled", <value>: Array[0] }
-
處理錯誤,常規情況下,當其中一個實例返回
rejected
,就會調用Promise.all
的catch
方法,返回第一個錯誤。但實際應用時,我們想讓所有的實例不論成功或失敗就可以返回參數組成數組,這時就可以調用實例自身的catch
方法來規避這種情況。const p1 = new Promise((resolve, reject) => { resolve('hello'); //resolved }).then(result => result).catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('報錯了');//rejected }).then(result => result).catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result))// ["hello", Error: 報錯了] .catch(e => console.log(e));
- 當
p1會resolved
,p2首先會rejected
,但是p2有自己的catch方法,該方法返回的是一個新的 Promise 實例,p2指向的實際上是這個實例。該實例執行完catch方法後,也會變成resolved,導致Promise.all()方法參數裏面的兩個實例都會resolved,因此會調用then方法指定的回調函數,而不會調用catch方法指定的回調函數。
-
js原生實現Promise.all的原理
//在Promise類上添加一個all方法,接受一個傳進來的promise數組 Promise.all = function (promiseArrs) { return new Promise((resolve, reject) => { //返回一個新的Promise let arr = []; //定義一個空數組存放結果 let i = 0; function handleData(index, data) { //處理數據函數 arr[index] = data; i++; if (i === promiseArrs.length) { //當i等於傳遞的數組的長度時 resolve(arr); //執行resolve,並將結果放入 } } for (let i = 0; i < promiseArrs.length; i++) { //循環遍歷數組 promiseArrs[i].then((data) => { handleData(i, data); //將結果和索引傳入handleData函數 }, reject) } }) }
-
如果說all體驗不好,那我們也可以自己做一個some方法,表示全部失敗纔算失敗
Promise.some = function (promiseArrs) { return new Promise((resolve, reject) => { let arr = []; //定義一個空數組存放結果 let i = 0; function handleErr(index, err) { //處理錯誤函數 arr[index] = err; i++; if (i === promiseArrs.length) { //當i等於傳遞的數組的長度時 reject(err); //執行reject,並將結果放入 } } for (let i = 0; i < promiseArrs.length; i++) { //循環遍歷數組 promiseArrs[i].then(resolve, (e) => handleErr(i, e)) } }) }
-
Promise.allSettled -- 兼容性不友好
該方法和promise.all類似,就是解決all方法在處理錯誤時的不合理而出現的。其參數接受一個Promise的數組, 返回一個新的Promise, 唯一與all的不同在於, 其不會進行短路, 也就是說當Promise全部處理完成後我們可以拿到每個Promise的狀態, 而不管其是否處理成功。-
和all類似,當自身實例有catch回調時,每個實例狀態變爲
fulfilled
const p3 = new Promise((resolve, reject) => { resolve('hello'); //resolved }).then(result => result).catch(e => e); const p4 = new Promise((resolve, reject) => { throw new Error('報錯了');//rejected }).then(result => result).catch(e => e); Promise.allSettled([p3, p4]) .then(result => console.log(result)) .catch(e => console.log(e)); //.then的log //[{status: "fulfilled", value: "hello"},{status: "fulfilled", reason: Error: 報錯了 at <anonymous>:6:10 at new Promise (<anonymous>) at <anonymous>:5:13}]
-
沒有catch接收錯誤,返回自身的狀態和回調參數
const p5 = new Promise((resolve, reject) => { resolve('hello'); //resolved }).then(result => result) const p6 = new Promise((resolve, reject) => { throw new Error('報錯了');//rejected }).then(result => result) Promise.allSettled([p5, p6]) .then(result => console.log(result)) .catch(e => console.log(e)); //.then的log //[{status: "fulfilled", value: "hello"},{status: "rejected", reason: Error: 報錯了 at <anonymous>:6:10 at new Promise (<anonymous>) at <anonymous>:5:13}]
-
- Promise.race()
該方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例,其他特點和all很像,和all的區別在於:race方法好比是賽跑,幾個實例一起跑,誰先到就成功了,就resolve誰,或者誰跑到中途摔了出現異常狀況失敗了,就reject誰,不論成功還是失敗,就先捕獲第一個完成的。
-
捕獲第一個成功的實例回調函數
let p1 = Promise.resolve('1') let p2 = Promise.resolve('2') Promise.race([p1,p2]).then(res=>conseloe.log(res))// '1'
-
捕獲第一個結果
let p1 = Promise.resolve("1"); let p2 = Promise.reject("ERR2"); Promise.race([p1,p2]).then(res=>conseloe.log(res)) //Promise {<resolved>: "1"}
-
捕獲第一個錯誤
let p1 = Promise.reject("ERR1"); let p2 = Promise.reject("ERR2"); Promise.race([p1,p2]).catch(console.log) //Promise {<reject>: "ERR1"}
-
原生實現Promise.race()的設計原理
Promise._race = iterator =>{ return new Promise((resolve,reject)=>{ iterator.forEach(item=>{ Promise.resolve(item).then(resolve).catch(reject) }) }) }
- Promise.try-- 提案
在實際開發使用promise時,希望經過promise包裝後的函數內部代碼讓同步函數同步執行,異步函數異步執行,並且讓它們具有統一的 API
例:當同步函數被promise包裝後的執行順序改變。
let fn = () =>console.log('同步1');
Promise.resolve().then(fn)
console.log('同步2')
//log後
//'同步2'
//'同步1'
解決讓同步函數同步執行,異步函數異步執行現階段方法
-
方法一:使用async匿名函數,會立即執行裏面的async函數,因此如果f是同步的,就會得到同步的結果;如果f是異步的,就可以用then指定下一步,如果想捕獲錯誤,使用catch方法。
let fn = () =>console.log('同步1'); (async ()=>fn())() .then(resolve) .catch(err=>console.log(err)) console.log('同步2') //log後 //'同步1' //'同步2'
-
-
方法二:使用promise立即執行的匿名函數
let fn = () =>console.log('同步1'); ( () => new Promise( resolve => resolve(fn()) ))() console.log('同步2') //log後 //'同步1' //'同步2'
-
Promise.try的應用
該方法是用來模擬try
的代碼塊的,就像promise.catch
模擬的是catch
代碼塊。-
理解
try catch finally
try catch是JavaScript的異常處理機制,把可能出錯的代碼放在try語句塊中,如果出錯了,就會被catch捕獲來處理異常。如果不catch 一旦出錯就會造成程序崩潰。finally:無論結果如何,允許在 try 和 catch 之後執行代碼。
try { // 供測試的代碼塊 } catch(err) { //處理錯誤的代碼塊 } finally { //無論 try / catch 結果如何都執行的代碼塊 }
-
應用
let fn = () => console.log('同步1'); Promise.try(fn); console.log('同步2'); //'同步1' //'同步2'
-
over~有問題留言
拓展:
- 什麼是Iterator ?
- 異步編程是什麼? 異步編程都有哪些解決方案?
- 如何使用promise實現ajax,封裝axios?
- 如何使用純原生js實現promise?
借鑑:
https://blog.csdn.net/sjw1039...
http://es6.ruanyifeng.com/#do...
https://developer.mozilla.org...