這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一、介紹
Promise
,譯為承諾,是非同步程式設計的一種解決方案,比傳統的解決方案(回撥函式)更加合理和更加強大
在以往我們如果處理多層非同步操作,我們往往會像下面那樣編寫我們的程式碼
doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('得到最終結果: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback);
閱讀上面程式碼,是不是很難受,上述形成了經典的回撥地獄
現在透過Promise
的改寫上面的程式碼
doSomething().then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('得到最終結果: ' + finalResult); }) .catch(failureCallback);
瞬間感受到promise
解決非同步操作的優點:
- 鏈式操作減低了編碼難度
- 程式碼可讀性明顯增強
下面我們正式來認識promise
:
狀態
promise
物件僅有三種狀態
pending
(進行中)fulfilled
(已成功)rejected
(已失敗)
#特點
- 物件的狀態不受外界影響,只有非同步操作的結果,可以決定當前是哪一種狀態
- 一旦狀態改變(從
pending
變為fulfilled
和從pending
變為rejected
),就不會再變,任何時候都可以得到這個結果
流程
認真閱讀下圖,我們能夠輕鬆瞭解promise
整個流程
二、用法
Promise
物件是一個建構函式,用來生成Promise
例項
const promise = new Promise(function(resolve, reject) {});
Promise
建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve
和reject
resolve
函式的作用是,將Promise
物件的狀態從“未完成”變為“成功”reject
函式的作用是,將Promise
物件的狀態從“未完成”變為“失敗”
例項方法
Promise
構建出來的例項存在以下方法:
- then()
- catch()
- finally()
then()
then
是例項狀態發生改變時的回撥函式,第一個引數是resolved
狀態的回撥函式,第二個引數是rejected
狀態的回撥函式
then
方法返回的是一個新的Promise
例項,也就是promise
能鏈式書寫的原因
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
catch
catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名,用於指定發生錯誤時的回撥函式
getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 處理 getJSON 和 前一個回撥函式執行時發生的錯誤 console.log('發生錯誤!', error); });
Promise
物件的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止
getJSON('/post/1.json').then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { // some code }).catch(function(error) { // 處理前面三個Promise產生的錯誤 });
一般來說,使用catch
方法代替then()
第二個引數
Promise
物件丟擲的錯誤不會傳遞到外層程式碼,即不會有任何反應
const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行會報錯,因為x沒有宣告 resolve(x + 2); }); };
瀏覽器執行到這一行,會列印出錯誤提示ReferenceError: x is not defined
,但是不會退出程序
catch()
方法之中,還能再丟擲錯誤,透過後面catch
方法捕獲到
finally()
finally()
方法用於指定不管 Promise 物件最後狀態如何,都會執行的操作
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
建構函式方法
Promise
建構函式存在以下方法:
- all()
- race()
- allSettled()
- resolve()
- reject()
- try()
all()
Promise.all()
方法用於將多個 Promise
例項,包裝成一個新的 Promise
例項
const p = Promise.all([p1, p2, p3]);
接受一個陣列(迭代物件)作為引數,陣列成員都應為Promise
例項
例項p
的狀態由p1
、p2
、p3
決定,分為兩種:
- 只有
p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態才會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個陣列,傳遞給p
的回撥函式 - 只要
p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的例項的返回值,會傳遞給p
的回撥函式
注意,如果作為引數的 Promise
例項,自己定義了catch
方法,那麼它一旦被rejected
,並不會觸發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
方法,就會呼叫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: 報錯了
race()
Promise.race()
方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項
const p = Promise.race([p1, p2, p3]);
只要p1
、p2
、p3
之中有一個例項率先改變狀態,p
的狀態就跟著改變
率先改變的 Promise 例項的返回值則傳遞給p
的回撥函式
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);
allSettled()
Promise.allSettled()
方法接受一組 Promise 例項作為引數,包裝成一個新的 Promise 例項
只有等到所有這些引數例項都返回結果,不管是fulfilled
還是rejected
,包裝例項才會結束
const promises = [ fetch('/api-1'), fetch('/api-2'), fetch('/api-3'), ]; await Promise.allSettled(promises); removeLoadingIndicator();
resolve()
將現有物件轉為 Promise
物件
Promise.resolve('foo') // 等價於 new Promise(resolve => resolve('foo'))
引數可以分成四種情況,分別如下:
- 引數是一個 Promise 例項,
promise.resolve
將不做任何修改、原封不動地返回這個例項 - 引數是一個
thenable
物件,promise.resolve
會將這個物件轉為Promise
物件,然後就立即執行thenable
物件的then()
方法 - 引數不是具有
then()
方法的物件,或根本就不是物件,Promise.resolve()
會返回一個新的 Promise 物件,狀態為resolved
- 沒有引數時,直接返回一個
resolved
狀態的 Promise 物件
reject()
Promise.reject(reason)
方法也會返回一個新的 Promise 例項,該例項的狀態為rejected
const p = Promise.reject('出錯了'); // 等同於 const p = new Promise((resolve, reject) => reject('出錯了')) p.then(null, function (s) { console.log(s) }); // 出錯了
Promise.reject()
方法的引數,會原封不動地變成後續方法的引數
Promise.reject('出錯了') .catch(e => { console.log(e === '出錯了') }) // true
三、使用場景
將圖片的載入寫成一個Promise
,一旦載入完成,Promise
的狀態就發生變化
const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); };
透過鏈式操作,將多個渲染資料分別給個then
,讓其各司其職。或當下個非同步請求依賴上個請求結果的時候,我們也能夠透過鏈式操作友好解決問題
// 各司其職 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()
實現多個請求合併在一起,彙總所有請求結果,只需設定一個loading
即可
function initLoad(){ // loading.show() //載入loading Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{ console.log(res) loading.hide() //關閉loading }).catch(err=>{ console.log(err) loading.hide()//關閉loading }) } //資料初始化 initLoad()
透過race
可以設定圖片請求超時
//請求某個圖片資源 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; } //延時函式,用於給請求計時 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); });
參考文獻
- https://es6.ruanyifeng.com/#docs/promise