Promise,譯為承諾,是異步編程的一種解決方案,比傳統(tǒng)的解決方案(回調(diào)函數(shù))更加合理和更加強大
在以往我們?nèi)绻幚矶鄬赢惒讲僮鳎覀兺鶗裣旅婺菢泳帉懳覀兊拇a
doSomething(function(result) {? doSomethingElse(result, function(newResult) {??? doThirdThing(newResult, function(finalResult) {????? console.log('得到最終結(jié)果: ' + finalResult);??? }, failureCallback);? }, failureCallback);}, failureCallback);
閱讀上面代碼,是不是很難受,上述形成了經(jīng)典的回調(diào)地獄
現(xiàn)在通過Promise的改寫上面的代碼
doSomething().then(function(result) {? return doSomethingElse(result);}).then(function(newResult) {? return doThirdThing(newResult);}).then(function(finalResult) {? console.log('得到最終結(jié)果: ' + finalResult);}).catch(failureCallback);
瞬間感受到promise解決異步操作的優(yōu)點:
[if !supportLists]?????????????[endif]鏈?zhǔn)讲僮鳒p低了編碼難度
[if !supportLists]?????????????[endif]代碼可讀性明顯增強
下面我們正式來認(rèn)識promise:
promise對象僅有三種狀態(tài)
[if !supportLists]?????????????[endif]pending(進行中)
[if !supportLists]?????????????[endif]fulfilled(已成功)
[if !supportLists]?????????????[endif]rejected(已失敗)
[if !supportLists]?????????????[endif]對象的狀態(tài)不受外界影響,只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài)
[if !supportLists]?????????????[endif]一旦狀態(tài)改變(從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected),就不會再變,任何時候都可以得到這個結(jié)果
認(rèn)真閱讀下圖,我們能夠輕松了解promise整個流程
[if !vml]
[endif]
Promise對象是一個構(gòu)造函數(shù),用來生成Promise實例
const promise = new Promise(function(resolve, reject) {});
Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是resolve和reject
[if !supportLists]?????????????[endif]resolve函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α?/p>
[if !supportLists]?????????????[endif]reject函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 ?/p>
Promise構(gòu)建出來的實例存在以下方法:
[if !supportLists]?????????????[endif]then()
[if !supportLists]?????????????[endif]catch()
[if !supportLists]?????????????[endif]finally()
then是實例狀態(tài)發(fā)生改變時的回調(diào)函數(shù),第一個參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個參數(shù)是rejected狀態(tài)的回調(diào)函數(shù)
then方法返回的是一個新的Promise實例,也就是promise能鏈?zhǔn)綍鴮懙脑?/p>
getJSON("/posts.json").then(function(json) {? return json.post;}).then(function(post) {? // ...});
catch()方法是.then(null,
rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)
getJSON('/posts.json').then(function(posts) {? // ...}).catch(function(error) {? // 處理getJSON 和 前一個回調(diào)函數(shù)運行時發(fā)生的錯誤? console.log('發(fā)生錯誤!', error);});
Promise對象的錯誤具有“冒泡”性質(zhì),會一直向后傳遞,直到被捕獲為止
getJSON('/post/1.json').then(function(post) {? return getJSON(post.commentURL);}).then(function(comments) {? // some code}).catch(function(error) {? // 處理前面三個Promise產(chǎn)生的錯誤});
一般來說,使用catch方法代替then()第二個參數(shù)
Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應(yīng)
const someAsyncThing = function() {? return new Promise(function(resolve, reject) {??? // 下面一行會報錯,因為x沒有聲明??? resolve(x + 2);? });};
瀏覽器運行到這一行,會打印出錯誤提示ReferenceError: x is not defined,但是不會退出進程
catch()方法之中,還能再拋出錯誤,通過后面catch方法捕獲到
finally()方法用于指定不管 Promise 對象最后狀態(tài)如何,都會執(zhí)行的操作
promise.then(result => {···}).catch(error => {···}).finally(() => {···});
Promise構(gòu)造函數(shù)存在以下方法:
[if !supportLists]?????????????[endif]all()
[if !supportLists]?????????????[endif]race()
[if !supportLists]?????????????[endif]allSettled()
[if !supportLists]?????????????[endif]resolve()
[if !supportLists]?????????????[endif]reject()
[if !supportLists]?????????????[endif]try()
Promise.all()方法用于將多個 Promise實例,包裝成一個新的 Promise實例
const p = Promise.all([p1, p2, p3]);
接受一個數(shù)組(迭代對象)作為參數(shù),數(shù)組成員都應(yīng)為Promise實例
實例p的狀態(tài)由p1、p2、p3決定,分為兩種:
[if !supportLists]?????????????[endif]只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時p1、p2、p3的返回值組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)
[if !supportLists]?????????????[endif]只要p1、p2、p3之中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調(diào)函數(shù)
注意,如果作為參數(shù)的 Promise 實例,自己定義了catch方法,那么它一旦被rejected,并不會觸發(fā)Promise.all()的catch方法
const p1 = new Promise((resolve, reject) => {? resolve('hello');}).then(result => result).catch(e => e);
const p2 = new Promise((resolve, reject) => {? throw new Error('報錯了');}).then(result => result).catch(e => e);
Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));
//
["hello", Error:
報錯了]
如果p2沒有自己的catch方法,就會調(diào)用Promise.all()的catch方法
const p1 = new Promise((resolve, reject) => {? resolve('hello');}).then(result => result);
const p2 = new Promise((resolve, reject) => {? throw new Error('報錯了');}).then(result => result);
Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));
// Error:
報錯了
Promise.race()方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例
const p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一個實例率先改變狀態(tài),p的狀態(tài)就跟著改變
率先改變的 Promise 實例的返回值則傳遞給p的回調(diào)函數(shù)
const p = Promise.race([? fetch('/resource-that-may-take-a-while'),? new Promise(function (resolve, reject) {??? setTimeout(() => reject(new Error('request timeout')), 5000)? })]);
p.then(console.log).catch(console.error);
Promise.allSettled()方法接受一組 Promise 實例作為參數(shù),包裝成一個新的Promise 實例
只有等到所有這些參數(shù)實例都返回結(jié)果,不管是fulfilled還是rejected,包裝實例才會結(jié)束
const promises = [? fetch('/api-1'),? fetch('/api-2'),? fetch('/api-3'),];
await Promise.allSettled(promises);removeLoadingIndicator();
將現(xiàn)有對象轉(zhuǎn)為 Promise對象
Promise.resolve('foo')
//
等價于new Promise(resolve => resolve('foo'))
參數(shù)可以分成四種情況,分別如下:
[if !supportLists]?????????????[endif]參數(shù)是一個 Promise 實例,promise.resolve將不做任何修改、原封不動地返回這個實例
[if !supportLists]?????????????[endif]參數(shù)是一個thenable對象,promise.resolve會將這個對象轉(zhuǎn)為 Promise對象,然后就立即執(zhí)行thenable對象的then()方法
[if !supportLists]?????????????[endif]參數(shù)不是具有then()方法的對象,或根本就不是對象,Promise.resolve()會返回一個新的 Promise 對象,狀態(tài)為resolved
[if !supportLists]?????????????[endif]沒有參數(shù)時,直接返回一個resolved狀態(tài)的 Promise 對象
Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態(tài)為rejected
const p = Promise.reject('出錯了');
//
等同于const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {? console.log(s)});
//
出錯了
Promise.reject()方法的參數(shù),會原封不動地變成后續(xù)方法的參數(shù)
Promise.reject('出錯了').catch(e => {? console.log(e === '出錯了')})
// true
將圖片的加載寫成一個Promise,一旦加載完成,Promise的狀態(tài)就發(fā)生變化
const preloadImage = function (path) {? return new Promise(function (resolve, reject) {??? const image = new Image();??? image.onload? = resolve;??? image.onerror = reject;??? image.src = path;? });};
通過鏈?zhǔn)讲僮鳎瑢⒍鄠€渲染數(shù)據(jù)分別給個then,讓其各司其職。或當(dāng)下個異步請求依賴上個請求結(jié)果的時候,我們也能夠通過鏈?zhǔn)讲僮饔押媒鉀Q問題
// 各司其職getInfo().then(res=>{??? let { bannerList } = res??? //渲染輪播圖??? console.log(bannerList)??? return res}).then(res=>{??? ??? let { storeList } = res??? //渲染店鋪列表??? console.log(storeList)??? return res}).then(res=>{??? let { categoryList } = res??? console.log(categoryList)??? //渲染分類列表??? return res})
通過all()實現(xiàn)多個請求合并在一起,匯總所有請求結(jié)果,只需設(shè)置一個loading即可
function initLoad(){??? // loading.show() //加載loading??? Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{??????? console.log(res)??????? loading.hide() //關(guān)閉loading??? }).catch(err=>{??????? console.log(err)??????? loading.hide()//關(guān)閉loading??? })}
//
數(shù)據(jù)初始化??? initLoad()
通過race可以設(shè)置圖片請求超時
//請求某個圖片資源function requestImg(){??? var p = new Promise(function(resolve, reject){??????? var img = new Image();??????? img.onload = function(){?????????? resolve(img);??????? }??????? //img.src =
"https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正確的??????? img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";??? });??? return p;}
//
延時函數(shù),用于給請求計時function timeout(){??? var p = new Promise(function(resolve, reject){??????? setTimeout(function(){??????????? reject('圖片請求超時');??????? }, 5000);??? });??? return p;}
Promise.race([requestImg(), timeout()]).then(function(results){??? console.log(results);}).catch(function(reason){??? console.log(reason);});
[if !supportLists]?????????????[endif]https://es6.ruanyifeng.com/#docs/promise