当前位置:网站首页>硬核解析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
边栏推荐
- Crawler for querying nicknames and avatars based on qqwebapi
- I/O多路复用及其相关详解
- Linux installs MySQL in RPM (super simple)
- Vulnérabilité d'exécution de la commande de fond du panneau de commande JD - freefuck
- PowerDesigner various font settings; Preview font setting; SQL font settings
- Closure type of rust (difference between FN, fnmut and fnone)
- Notes on common basic usage of eigen Library
- Crawl lottery data
- [UDS unified diagnostic service] (Supplement) v. detailed explanation of ECU bootloader development points (1)
- Romance in C language
猜你喜欢
positioner
A few lines of code teach you to crawl lol skin pictures
WIN1 remote "this may be due to credssp encryption Oracle correction" solution
深度学习经典网络解析目标检测篇(一):R-CNN
[UDS unified diagnostic service] IV. typical diagnostic service (4) - online programming function unit (0x34-0x38)
C language loop structure program
【ACM】509. 斐波那契数(dp五部曲)
From source code to executable file
JD-FreeFuck 京东薅羊毛控制面板 后台命令执行漏洞
Implementation of k8s redis one master multi slave dynamic capacity expansion
随机推荐
Install the yapiupload plug-in in idea and upload the API interface to the Yapi document
Pointers in rust: box, RC, cell, refcell
C [file operation] read TXT text by line
Rust: how to implement a thread pool?
【ACM】455. Distribute Biscuits (1. Give priority to big biscuits to big appetite; 2. Traverse two arrays with only one for loop (use subscript index -- to traverse another array))
Correct opening method of option
【ACM】455. 分发饼干(1. 大饼干优先喂给大胃口;2. 遍历两个数组可以只用一个for循环(用下标索引--来遍历另一个数组))
Process management command
Box pointer of rust
From source code to executable file
The difference between deep copy and shallow copy
journal
idea中安装YapiUpload 插件将api接口上传到yapi文档上
k8s之实现redis一主多从动态扩缩容
深度学习经典网络解析目标检测篇(一):R-CNN
多功能工具箱微信小程序源码
【ACM】70. 爬楼梯
Re regular expression
Generate verification code
Multi thread crawling Marco Polo network supplier data