当前位置:网站首页>硬核解析Promise對象(這七個必會的常用API和七個關鍵問題你都了解嗎?)
硬核解析Promise對象(這七個必會的常用API和七個關鍵問題你都了解嗎?)
2022-04-23 18:12:00 【小傑學前端】
目錄
Promise 構造函數: Promise (excutor) {}
一、Promise的理解與使用
Promise的概念
Promise 是异步編程的一種解决方案,而我們之前的解决方案是啥呢?沒錯,就是回調函數。Promise要比它更合理和更强大。
這樣解釋還不够清楚明白,那Promise到底是個啥呢
簡單說就是一個容器,裏面保存著某個未來才會結束的事件(通常是一個异步操作)的結果。
通俗講,Promise翻譯過來就是承諾,它是對未來事情的承諾,承諾不一定能完成(就像你答應你女朋友未來會給她買包,只是想先應付一下,以後買不買不一定呢),但是無論是否能完成都會有一個結果。
Promise的使用
Promise 是异步編程的一種解决方案,主要用來解决回調地獄的問題,可以有效减少回調嵌套。
從語法上說:Promise就是一個構造函數
從功能上說:Promise對象用來封裝一個异步操作並可以獲取其成功/失敗的返回值
首先我們先想一下都有哪些是异步操作
fs文件操作 數據庫操作 ajax請求操作 定時器
上面都是异步操作
fs文件操作 require('fs').readFile('./xiaojie.txt',(err,data)=>{})
數據庫操作 $.get('/server',(data)=>{})
定時器 setTimeout(() => {}, 2000);
在promise前,我們進行异步操作時,用的都是純回調函數的方式來進行的,那我們為什麼使用promise呢,使用回調函數不行嗎
注意,這是最重要的,面試必問
因為Promise支持鏈式調用,可以解决回調地獄問題
回調地獄問題
那麼什麼是回調地獄呢 為啥要解决他呢 Promise又是怎麼解决的呢
下面我們一個一個來解决:
首先我們看下面這段代碼,我們想分三次輸出數字,每次間隔一秒鐘,那我們肯定得這麼做:
setTimeout(function () {
console.log(111);
setTimeout(function () {
console.log(222);
setTimeout(function () {
console.log(333);
}, 1000);
}, 1000);
}, 1000);
代碼嵌套,在一個複雜的程序當中,是非常不便於閱讀的並且不便於异常處理,因為在每一層我們都需要對錯誤進行异常處理,可能會寫很多重複的代碼而且會讓縮進格式變得非常冗贅,當嵌套層數過多,會出現“回調地獄”。在這裏就體現出用一種新的方式書寫代碼有多重要。用promise方法去處理這類問題,無疑會更合適。
解决方案
promise鏈式調用
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(111);
resolve();
}, 1000);
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(222);
resolve();
}, 1000);
});
}).then(() => {
setTimeout(() =>{
console.log(333);
}, 1000);
});
這樣最後的輸出效果是相同的,而且會把嵌套的代碼語句書寫改變為按序書寫,但不改變其執行的异步過程,這正是promise方法的作用及優點。
Promise的基本用法
ES6 規定,Promise 是一個構造函數,即然是構造函數那他就可以進行對象實例化。
promise在對象實例化的時候需要接受參數,這個參數是一個函數類型的值
該函數的兩個參數分別是 resolve 和 reject 。
resolve是解决的意思,reject是拒絕的意思。
這兩個都是函數類型的數據
當异步任務成功時調用resolve,當异步任務失敗時調用reject
下面代碼創造了一個Promise實例。
const p = new Promise((resolve, reject) => {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve() 在調用完後,可以將promise對象(p)的狀態設置為成功
reject() 相反,調用完後,可以將promise對象(p)的狀態設置為失敗
我們創建完promise實例後需要調用then方法,他在執行時需要接收兩個參數,而且兩個參數都是函數類型的值
p.then((value) => {},(error) => {});
第一個參數是對象成功時的回調,第二個參數是對象失敗時的回調。
這兩個參數都是可選的,不一定要提供。它們都接受 Promise 對象傳出的值作為參數。
通俗的說,如果promise對象狀態成功就執行第一個回調函數,如果狀態失敗就執行第二個回調函數
Promise實踐初體驗
我們先不展開講解Promise的各種API和原理,先做兩個實踐應用,簡單體驗一下Promise。
fs模塊讀取文件
這個需要我們安裝node.js環境,fs模塊的作用就是可以對計算機的硬盤進行讀寫操作
我們的案例要求是這樣的:
讀取當前目錄下的nba.txt文件裏的內容:
傳統回調函數形式:
//引入fs模塊
const fs = require('fs');
fs.readFile('./nba.txt', (err, data) => {
// 如果出錯 則拋出錯誤
if(err) throw err;
//沒有出錯就輸出文件內容
console.log(data.toString());
});
PS D:\用戶目錄\Desktop\home\html (2)\4.20> node zyj.js
勇士總冠軍
這樣的話我們的nba.txt裏的內容就輸出出來了
Promise封裝形式:
//封裝一個函數 mineReadFile 讀取文件內容
//參數: path 文件路徑
//返回: promise 對象
function mineReadFile(path){
return new Promise((resolve, reject) => {
//讀取文件
require('fs').readFile(path, (err, data) =>{
//判斷
if(err) reject(err);
//成功
resolve(data);
});
});
}
mineReadFile('./nba.txt')
.then(value=>{
//輸出文件內容
console.log(value.toString());
}, error=>{
console.log(error);
});
如果成功的話data就是一個buffer需要tostring方法把它轉化為字符串,如果出錯就把錯誤的對象打印出來
當我們輸出正常時:
PS D:\用戶目錄\Desktop\home\html (2)\4.20> node zyj.js
勇士總冠軍
我們故意把路徑打錯,看看錯誤會輸出什麼:
PS D:\用戶目錄\Desktop\home\html (2)\4.20> node zyj.js
[Error: ENOENT: no such file or directory, open 'D:\用戶目錄\Desktop\home\html (2)\4.20\na.txt'] {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'D:\\用戶目錄\\Desktop\\home\\html (2)\\4.20\\na.txt'
}
輸出的就是error這個錯誤對象裏的內容
使用 promise 封裝 ajax 异步請求
<script >
/*可複用的發 ajax 請求的函數: xhr + promise*/
function promiseAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
// 請求成功, 調用 resolve(value)
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else { // 請求失敗, 調用 reject(reason)
reject(xhr.status);
}
}
})
}
promiseAjax('http://www.liulongbin.top:3006/api/getbooks').then(data => {
console.log('顯示成功數據', data)
},
error => {
alert(error)
})
</script>
輸出結果:
當我們故意把地址打錯:
promise對象狀態屬性
Promise 對象的狀態不受外界影響。Promise 對象有三種狀態:
pending (進行中)、fulfilled (已成功) 和 rejected (已失敗)。
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
Promise對象的狀態改變,只有兩種可能:從 pending 變為 fulfilled(resolved)和從 pending 變為 rejected。
只要這兩種情况發生,狀態就凝固了,不會再變了,會一直保持這個結果。
只有异步操作的結果,可以决定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,錶示其他手段無法改變。
如果改變已經發生了,你再對
Promise
對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
promise對象結果值屬性
這是 promise 實例對象中的另一個屬性 [promiseResult] ,保存著异步任務 [成功/失敗] 的結果
resolve和reject兩個方法是可以對實例對象的結果屬性進行修改,除了他倆誰也改不了
小案例
在對Promise有個大概了解後,我們看一下下面的代碼:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
分析:
上面代碼中,timeout 方法返回一個Promise實例對象,錶示一段時間以後才會發生的結果。過了指定的時間(ms參數)以後, 計時器會調用 resolve(),並且把結果值(done)傳進來,在調用完後,可以將promise對象(p)的狀態設置為成功 ,他的狀態改變了就會觸發then方法綁定的回調函數,把傳進來的結果值(done)輸出出來。
二、Promise API
Promise 構造函數: Promise (excutor) {}
(1) executor 函數: 執行器 (resolve, reject) => {}
(2) resolve 函數: 內部定義成功時我們調用的函數 value => {}
(3) reject 函數: 內部定義失敗時我們調用的函數 reason => {}
重點:executor 會在 Promise 內部立即同步調用
我們來看下面這段代碼
let p = new Promise(function(resolve, reject) {
console.log('jack');
resolve();
});
p.then(value=> {
console.log('andy');
});
console.log('Hi!');
// jack
// Hi!
// andy
上面代碼中,Promise 新建後立即執行,所以首先輸出的是 'jack'。然後,then
方法指定的回調函數,將在當前脚本所有同步任務執行完才會執行,所以再輸出 'Hi','andy' 最後輸出。
實際上,這個運行結果相關知識點是 [ 宏任務與微任務 ] ,單獨梳理在下方:
- 宏任務: setTimeout , setInterval , Ajax , DOM事件
- 微任務: Promise async/await
- 微任務執行時機比宏任務要早(先記住)
下面我們看一道面試題:
console.log(100);
setTimeout(()=>{
console.log(200);
})
setTimeout(()=>{
console.log(201);
})
Promise.resolve().then(()=>{
console.log(300);
})
console.log(400);
// 100 400 300 200 201
大家都做對了嗎,如果沒有做對的話,再結合一下JS的運行機制就可以輕松理解了:
- 所有的同步任務都在主線程上執行,行成一個執行棧。
- 除了主線程之外,還存在一個任務列隊,只要异步任務有了運行結果,就在任務列隊中植入一個時間標記。
- 主線程完成所有任務(執行棧清空),就會讀取任務列隊,先執行微任務隊列在執行宏任務隊列。
- 重複上面三步。
Promise.prototype.then()
then方法是定義在原型對象Promise.prototype上的。它的作用是為 Promise 實例添加狀態改變時的回調函數
then方法方法返回的是一個新的Promise實例
then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以采用鏈式寫法,即then方法後面再調用另一個then方法。
let p = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'jack');
});
p.then(value => {
console.log(value);
return 'andy';
}).then(value => console.log(value));
//jack
//andy
then()方法會返回一個新的Promise對象, 所以then後面可以繼續跟一個then方法進行鏈式調用, 如果then方法的回調函數有返回數據,那麼這個數據會作為下一個then的回調函數的參數,這塊知識點涉及到鏈式調用,在Promise關鍵問題第五個串聯多個任務裏有詳細講解。大家在這裏先做一個簡單了解。
采用鏈式的then, 會等待前一個Promise狀態發生改變才會被調用
then的回調函數也可能會返回一個Promise對象,這個時候就會覆蓋then自己返回的Promise對象,這時後一個回調函數,就會等待該Promise對象的狀態發生變化,才會被調用。
let p = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'jack');
});
p.then(value => {
console.log(value);
return new Promise((resolve,reject) => {
setTimeout(resolve,1000,'andy');
});
}).then(value => console.log(value));
//jack
//andy
上面代碼中,第一個then方法指定的回調函數,返回的是另一個Promise對象。這時,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。
因為第二個回調函數有一個定時器,所以一秒鐘後狀態變為resolved,第二個then方法看到狀態改變了才會去執行log輸出語句
Promise.prototype.catch()
Promise.prototype.catch()方法是 .then(null, rejection) 或 .then(undefined, rejection) 的別名,用於指定發生錯誤時的回調函數。
let p = new Promise((resolve, reject) => {
reject('what is the matter');
});
p.catch(error => {
console.log(error);
})
//what is the matter
如果Promise 對象狀態變為rejected,就會調用 catch() 方法指定的回調函數,處理這個錯誤。
let p = new Promise((resolve, reject) => {
throw new Error('拋出了錯誤')
});
p.catch(error => {
console.log(error);
})
//Error: 拋出了錯誤
上面代碼中,Promise拋出一個錯誤,同樣會被 catch() 方法指定的回調函數捕獲。
值得注意的是:如果 Promise 狀態已經變成 resolved ,再拋出錯誤是無效的。
let p = new Promise((resolve, reject) => {
resolve('success');
throw new Error('拋出了錯誤')
});
p.then(value => {
console.log(value);
}).catch(error => {
console.log(error);
})
//success
上面代碼中,拋出錯誤在 resolve 語句的後面,那就不會被捕獲,等於沒有拋出。因為 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。
Promise 對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個 catch 語句捕獲。
我們知道每個then方法都會產生一個新的Promise實例,如果一個Promise對象後面緊跟著多個then方法,它們之中任何一個拋出的錯誤,都會被最後一個catch()捕獲。
重點:所以,不要在 then() 方法裏面定義 Reject 狀態的回調函數(即 then 的第二個參數),最 好使用catch方法,這樣可以捕獲前面then方法執行中的錯誤。
Promise.resolve()
有時需要將現有對象轉為 Promise 對象,Promise.resolve() 方法就起到這個作用。
let p = Promise.resolve('jack');
console.log(p);
打開控制臺,看看上面這段代碼輸出了什麼:
他的結果是一個Promise對象,而且狀態為成功,成功的值為我們傳入的參數 'jack' 。
Promise.resolve() 等價於下面的寫法:
Promise.resolve('jack')
// 等價於
new Promise(resolve => {resolve('jack')})
Promise.resolve() 方法的參數分成兩種情况:
參數是一個Promise實例:
如果參數是 Promise 實例,那麼Promise.resolve() 將不做任何修改、原封不動地返回這個實例。
let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(p1);
console.log(p2);
//Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 'jack'}
參數不是Promise對象或沒有任何參數:
Promise.resolve() 方法返回一個新的 Promise 對象,狀態為 resolved
const p = Promise.resolve();
p.then(function () {
// ...
});
上面代碼的變量 p 就是一個 Promise 對象。
需要注意的是,立即 resolve() 的 Promise 對象,是在本輪“事件循環”(event loop)的結束時執行,而不是在下一輪“事件循環”的開始時。
setTimeout(function () {
console.log('jack');
}, 0);
Promise.resolve().then(function () {
console.log('andy');
});
console.log('david');
// david
// andy
// jack
上面代碼中,setTimeout( fn , 0) 下一輪“事件循環”開始時執行,Promise.resolve() 在本輪“事件循環”結束時執行,console.log('david') 是立即執行,因此最先輸出。
Promise.reject()
Promise.reject(reason) 方法也會返回一個新的 Promise 實例,該實例的狀態為 rejected 。
let p1 = Promise.reject('出錯啦');
console.log(p1);
//Promise { <rejected> '出錯啦' }
const p = Promise.reject('出錯啦');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯啦'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
即便我們傳入的參數是一個成功的Promise對象,他的結果也是失敗:
let p1 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok');
}));
console.log(p1);
//Promise { <rejected> Promise { 'ok' } }
此時我們的Promise對象p1的狀態是失敗,它失敗的結果是我們傳入的這個值,也就是這個成功的Promise對象,簡單理解就是,不論怎樣,Promise.reject()返回的狀態一定是rejected
Promise.all()
Promise.all() 方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]);
- Promise.all() 方法接受一個數組作為參數,
- p1, p2, p3 都是 Promise 實例,如果不是,就會先調用Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。
let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(111);
const p3 = Promise.all([p1,p2,'andy']);
console.log(p3);
這裏我們定義了兩個狀態為成功的Promise對象 p1和p2,我們想通過Promise.all() 方法將多個 Promise 實例,包裝成一個新的 Promise 實例 p3,但是參數數組裏的最後一個元素並不是Promise對象而是一個字符串,那麼 p3 會輸出什麼呢?
輸出結果如上圖所示,就如我們前面提到的,如果數組內有元素不是Promise實例,會先調用Promise.resolve方法,將參數轉為 Promise 實例,再進行處理。
p 的狀態由 p1, p2, p3 决定,分成兩種情况:
1. 只有 p1, p2, p3 的狀態都變成 fulfilled ,p 的狀態才會變成 fulfilled ,此時 p1, p2, p3 的返回值組成一個數組,傳遞給p的回調函數。
2. 只要 p1, p2, p3 之中有一個為 rejected,p 的狀態就變成 rejected ,此時第一個被 reject 的實例的返回值,會傳遞給p的回調函數。
下面我們來分析一段代碼:
const p1 = new Promise((resolve, reject) => {
resolve('success');
}).then(value => value).catch(reason => reason);
const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
}).then(value => value).catch(reason => reason);
Promise.all([p1, p2]).then(value => {console.log(value)})
.catch(reason => {console.log(reason)});
上面代碼中,p1會resolved,p2首先會rejected,但是p2有自己的catch方法,該方法返回的是一個新的 Promise 實例,p2指向的實際上是這個實例。該實例執行完catch方法後,也會變成resolved,導致Promise.all() 方法參數裏面的兩個實例都會resolved,因此會調用then方法指定的回調函數,而不會調用catch方法指定的回調函數。
下面是這段代碼的輸出結果:
如果p2沒有自己的catch方法,就會調用 Promise.all() 的 catch 方法,這個時候輸出結果就是這樣的:
Promise.race()
Promise.race() 方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1 , p2 , p3 之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
我們分析一下下面一段代碼應該輸出什麼:
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok');
}, 1000);
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('oh year');
const result = Promise.race([p1,p2,p3]);
console.log(result);
我們先看一下p1,p2,p3他們三個誰先改變狀態,如果p1先改變狀態,那 result 結果就是由p1决定的,如果p1成功那麼result就成功,它成功的結果就是result成功的結果。但是因為在我們的例子裏p1是一個定時器,是一個异步任務,那肯定不是p1,順著往下排,p1不行那就肯定p2先改變狀態,我們在控制臺看看輸出的結果:
因為p2的狀態是成功,所以result的狀態就是fulfilled,因為p2結果是success,所以result結果也是result。
三、Promise關鍵問題
1.如何修改對象的狀態
- resolve(value): 如果當前是 pending 就會變為 resolved
- reject(reason): 如果當前是 pending 就會變為 rejected
- 拋出异常: 如果當前是 pending 就會變為 rejected(可以拋一個Error實例對象)
2.能否執行多個回調
當 promise 改變為對應狀態時都會調用
我們看下面這段代碼:
let p = new Promise((resolve, reject) => { resolve('OK');});
p.then(value => { console.log(value); });
p.then(value => { console.log(value+' again')});
我們給Promise對象p制定了兩個回調,那它都會執行麼?我們看下控制臺:
控制臺順序打印了兩個回調裏的輸出,當且僅當Promise對象改變為對應狀態時才會調用。
如果在Promise內部不調用resolve,那他的狀態就是pending,這樣我們沒有改變狀態所以一個then回調也不會執行
如果把這段代碼改成這樣,他還會是一樣的結果嗎:
let p = new Promise((resolve, reject) => { resolve('OK');});
p.then(value => { console.log(value);}).then(value => { console.log(value+' again');});
先看一下輸出結果:
為什麼第二個回調裏的value是undefined呢?
因為在上面then方法的詳解中提到過then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)
如果then方法的回調函數有返回數據,那麼這個數據會作為下一個then的回調函數的參數,但是我們上面例子裏的第一個then裏沒有return數據,所以第二個then裏就不知道value是誰,如果我們在第一個then裏加上return value,這樣輸出的結果就和原來一樣啦。
3.改變狀態與執行回調的順序問題
都有可能, 正常情况下是先指定回調再改變狀態, 但也可能先改狀態再指定回調
當Promise對象的執行器當中的任務是同步任務,直接調用resolve,這種情况就是先改變Promise對象的狀態,然後再去指定回調
如下例代碼:
let p = new Promise((resolve,reject) => {
resolve('success');
});
p.then(value => {console.log(value);});
那什麼時候then方法先執行,然後resolve改變狀態後執行呢?
就是當執行器函數中是异步任務的時候, 也就是說,我改變狀態需要等待一段時間,需要進入對應的异步任務隊列去執行的時候,在這種情况下就是then先執行,resolve改變狀態後執行
如下例代碼:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
p.then(value => {console.log(value);});
我們總結一下:
先指定回調再改變狀態(异步
):
- 先指定回調
- 再改變狀態
- 改變狀態後才進入异步隊列執行回調函數
先改狀態再指定回調(同步
):
- 改變狀態
- 指定回調 並馬上執行回調
那麼我們如何先改狀態再指定回調呢?
注意:指定並不是執行
- 在執行器中直接調用 resolve()/reject() ,不使用定時器等方法,執行器內直接同步操作
- 延遲更長時間才調用 then() ,在 .then() 這個方法外再包一層例如延時器這種方法
那我們什麼時候才能得到數據?
- 如果先指定的回調, 那當狀態發生改變時, 回調函數就會調用, 得到數據
- 如果先改變的狀態, 那當指定回調時, 回調函數就會調用, 得到數據
簡單地說,如果Promise對象的執行器裏是异步任務,比如說輸出語句在定時器裏的話,就先指定回調,但是指定不是執行。也得等Promise對象的狀態發生改變時,才回去調用回調函數。
4.then方法返回結果由什麼决定
前面我們說了,then方法方法返回的是一個新的Promise實例 ,那新的Promise對象的狀態由什麼來决定呢,它的返回結果的特點是什麼呢?
簡單錶達:新Promise對象的狀態由then()指定的回調函數執行的結果决定
詳細錶達:
1. 如果拋出异常, 新 promise 變為 rejected, reason 為拋出的异常
代碼示例:
let p = new Promise((resolve,reject) => {
resolve('success');
});
let result = p.then(value => {
throw '出錯啦';
})
console.log(result);
輸出結果:
2.如果返回的是非 promise 的任意值, 新 promise 變為 resolved, value 為返回的值
代碼示例:
let p = new Promise((resolve,reject) => {
resolve('success');
});
let result = p.then(value => {
return 'jack';
})
console.log(result);
輸出結果:
3.如果返回的是另一個新 promise, 此 promise 的結果就會成為新 promise 的結果
代碼示例:
let p = new Promise((resolve,reject) => {
resolve('success');
});
let result = p.then(value => {
return new Promise((resolve,reject) => {
reject('no');
})
})
console.log(result);
輸出結果:
5.串聯多個任務
1. promise 的 then()返回一個新的 promise, 可以看成 then()的鏈式調用
2. 通過 then 的鏈式調用串連多個同步/异步任務,這樣就能用then()將多個同步或异步操作串聯成一個同步隊列
我們先看下面這段代碼:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
return new Promise((resolve,reject) => {
resolve('success');
})
}).then(value => {
console.log(value);
})
輸出結果:
因為第一個then返回的是一個新的Promise對象, 那麼這個 promise 的結果就會成為新 promise 的結果,所以最後會輸出success。
接下來我們對上面的代碼再加一個then回調:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
return new Promise((resolve,reject) => {
resolve('success');
})
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
最後這個value的輸出會是什麼呢? 答案是 undefined
我們知道then的返回結果是一個Promise對象,它這個Promise對象的狀態是由它指定的回調函數的返回值來决定的,第二個then的回調函數的返回值是空,就是undefined,undefined顯然不是一個Promise類型的對象,我們之前說過,如果返回的是非 promise 的任意值, 新 promise 變為 resolved,也就是狀態是成功的,那他的成功的結果就是返回的結果undefined。因為第二個then返回的Promise對象成功了,就會執行最後一個then的第一個回調,並且輸出他成功的結果,也就是undefined。這就是then的鏈式調用
6.异常穿透
- 當使用 promise 的 then 鏈式調用時, 可以在最後指定失敗的回調
- 前面任何操作出了异常, 都會傳到最後失敗的回調中處理
示例代碼:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
reject('出錯啦');
},1000)
});
p.then(value => {
console.log(000);
}).then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).catch(error => {
console.warn(error);
})
我們先看一下輸出結果:
他輸出的結果剛好是第一個Promise當中他失敗的結果的值,中間的環節我們不需要管,不用指定失敗的回調,只需要在最後指定一個失敗的回調就可以處理失敗的結果。這種現象就屬於异常穿透。
比如我們在中間環節拋出一個錯誤,看看最後的catch能不能捕獲到 :
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
throw '出錯啦';
}).then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).catch(error => {
console.warn(error);
})
輸出結果:
第一個then回調裏拋出的錯誤是由最後的catch方法的回調來處理的,這就是异常穿透。
7.如何終斷Promise鏈
當promise狀態改變時,他的鏈式調用都會生效,那如果我們有這個一個實際需求:我們有4個then(),但其中有條件判斷,如當我符合或者不符合第2個then條件時,要直接中斷鏈式調用,不再走下面的then,該如何?
方法: 在該回調函數中返回一個 pending 狀態的 Promise 對象
示例代碼:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
return new Promise(()=>{})
}).then(value => {
console.log(333);
}).then(value => {
console.log(444);
})
輸出結果:
示例分析:
中斷Promise鏈的方法有且只有一種方式,那就是必須返回一個pending狀態的Promise對象。因為如果返回的是一個成功的Promise對象,就算返回的結果都是undefined但是then裏的回調還是會執行,唯獨只有pending狀態的Promise對象不一樣,因為如果回調裏返回一個pending,那麼這個then返回的也是狀態為pending的Promise對象,如果第一個(本題中)then返回的是pending狀態的Promise對象,那後續的then方法的回調就都不能執行,因為狀態沒有改變,狀態不改變就都不能執行。
簡單地說:把當前狀態變成pending,那麼後面的then和catch就不執行
到這裏我們Promise的第一部分就完成了,我們學習了Promise相關的API和一些關鍵問題,但是這些對於我們去大廠面試來說還遠遠不够。Promise的第二部分我們將試著去自定義封裝Promise。我們先學會如何怎麼去使用它,再從原理入手學著怎麼去實現它。
希望看到這裏的同學能够來個一鍵三連支持一下小傑同學!
版权声明
本文为[小傑學前端]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204231811052703.html
边栏推荐
- Data stream encryption and decryption of C
- Secure credit
- xlsxwriter. exceptions. Filecreateerror: [errno 13] permission denied
- Pointers in rust: box, RC, cell, refcell
- positioner
- powerdesigner各种字体设置;preview字体设置;sql字体设置
- Rust: a simple example of TCP server and client
- 【ACM】70. climb stairs
- 【ACM】376. Swing sequence
- Crawling mobile game website game details and comments (MQ + multithreading)
猜你喜欢
【ACM】509. Fibonacci number (DP Trilogy)
[UDS unified diagnostic service] (Supplement) v. detailed explanation of ECU bootloader development points (1)
Random number generation of C #
[UDS unified diagnostic service] v. diagnostic application example: Flash bootloader
【ACM】70. 爬楼梯
Differences between SSD hard disk SATA interface and m.2 interface (detailed summary)
Calculation of fishing net road density
C medium? This form of
MySQL 中的字符串函数
.105Location
随机推荐
re正則錶達式
[UDS unified diagnostic service] v. diagnostic application example: Flash bootloader
Data stream encryption and decryption of C
Robocode tutorial 8 - advanced robot
Map basemap Library
Queue solving Joseph problem
ArcGIS table to excel exceeds the upper limit, conversion failed
How to install jsonpath package
MySQL_ 01_ Simple data retrieval
Vulnérabilité d'exécution de la commande de fond du panneau de commande JD - freefuck
线上怎么确定期货账户安全的?
Flash operates on multiple databases
C byte array (byte []) and string are converted to each other
Implementation of object detection case based on SSD
Nodejs installation
Rust: the output information of println is displayed during the unit test
读取excel,int 数字时间转时间
Classification of cifar100 data set based on convolutional neural network
Re regular expression
Differences between SSD hard disk SATA interface and m.2 interface (detailed summary)