我終于弄懂了Promise

轉(zhuǎn)自:掘金 -?前端布吉島
https://juejin.cn/post/6921593620680802311
寫在前面
以前總是似懂非懂,這次總算把它弄了個(gè)清楚
什么是Promise
ES6 異步編程的一種解決方案,比傳統(tǒng)的方案(回調(diào)函數(shù)和事件)更加的合理和強(qiáng)大 好處 異步操作以同步操作的流程表達(dá)出來,避免了層層嵌套的回調(diào)函數(shù) promise可以解決異步的問題,本身不能說promise是異步的
Promise特點(diǎn)
對(duì)象的狀態(tài)不受外界影響。 Promise對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending(進(jìn)行中)、resolved(已成功)和rejected(已失敗)一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。 Promise對(duì)象的狀態(tài)改變,只有兩種可能:從pending變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">resolved和從pending變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">rejectedpromise內(nèi)部發(fā)生錯(cuò)誤,不會(huì)影響到外部程序的執(zhí)行。 無法取消 Promise。一旦新建它就會(huì)立即執(zhí)行,無法中途取消。其次,如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤,不會(huì)反應(yīng)到外部。第三,當(dāng)處于pending狀態(tài)時(shí),無法得知目前進(jìn)展到哪一個(gè)階段(剛剛開始還是即將完成)
用法
基礎(chǔ)用法
創(chuàng)造Promise實(shí)例時(shí),必須傳入一個(gè)函數(shù)作為參數(shù)
new?Promise(()?=>?{});
new?Promise();?//?報(bào)錯(cuò)
該函數(shù)可以接收另外兩個(gè)由JavaScript引擎提供的函數(shù),resolve和reject。函數(shù)作用:
resolve——將Promise對(duì)象的狀態(tài)從pending變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">resolved,將異步操作的結(jié)果,作為參數(shù)傳遞出去reject——將Promise對(duì)象的狀態(tài)從pending變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">rejected,將異步操作報(bào)出的錯(cuò)誤,作為參數(shù)傳遞出去
let?promise?=?new?Promise((resolve,?reject)?=>?{
??//?do?something
??if?(true)?{
????//?將參數(shù)返回,供then方法使用
????resolve("value");
??}?else?{
????//?將參數(shù)返回,供then方法使用
????reject("error");
??}
});
Promise實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。
promise.then(
??value?=>?{
????//?resolved時(shí)調(diào)用,value為resolve函數(shù)返回的參數(shù)
????console.log(value);
??},
??err?=>?{
????//?rejected時(shí)調(diào)用,err為reject函數(shù)返回的參數(shù)
????console.log(err);
??}
);
當(dāng)then方法只有一個(gè)函數(shù)參數(shù)時(shí),此時(shí)為resolved狀態(tài)的回調(diào)方法
promise.then(value?=>?{
????//?只有狀態(tài)為resolved時(shí)才能調(diào)用,如果返回的是rejected狀態(tài),則報(bào)錯(cuò)?Uncaught?(in?promise)?error
????console.log(value);
??});
只有當(dāng)promise的狀態(tài)變?yōu)閞esolved或者rejected時(shí),then方法才會(huì)被調(diào)用
Promise 新建后就會(huì)立即執(zhí)行,并且調(diào)用resolve或reject后不會(huì)終結(jié)?Promise的參數(shù)函數(shù)的執(zhí)行。
let?promise?=?new?Promise(function(resolve)?{
??console.log("Promise");
??resolve();
??console.log("!!!")
});
promise.then(function()?{
??console.log("resolved.");
});
console.log("Hi!");
//?Promise
//?!!!
//?Hi!
//?resolved
resolve返回的是另外一個(gè)Promise實(shí)例
const?p1?=?new?Promise((_,?reject)?=>?{
??setTimeout(()?=>?reject('error'),?3000);
});
const?p2?=?new?Promise(resolve?=>?{
??setTimeout(()?=>?resolve(p1),?1000);
});
p2.then(
??result?=>?console.log(result),
??error?=>?console.log(error)?//?error
);
上面代碼中,
p1是一個(gè)?Promise,3 秒之后變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">rejected。p2的狀態(tài)在 1 秒之后改變,resolve方法返回的是p1。由于p2返回的是另一個(gè) Promise,導(dǎo)致p2自己的狀態(tài)無效了,由p1的狀態(tài)決定p2的狀態(tài)。所以,后面的then語句都變成針對(duì)后者(p1)。又過了 2 秒,p1變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">rejected,導(dǎo)致觸發(fā)catch方法指定的回調(diào)函數(shù)。
以上是原文解釋,我們可以理解成p2.then?實(shí)際上是p1.then
Promise.prototype.then()
then方法是定義在原型對(duì)象Promise.prototype上的,同時(shí)then方法的兩個(gè)參數(shù)都是非必選的。因?yàn)閠hen方法返回的是一個(gè)**全新**的promise實(shí)例時(shí),因此then方法可以鏈?zhǔn)秸{(diào)用?then方法在以下情況返回的promise
當(dāng)未傳入?yún)?shù)時(shí),then方法會(huì)返回一個(gè)新的,狀態(tài)和原promise相同的promise
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
});
let?p?=?promise.then();
console.log(promise);
console.log(p);
結(jié)果展示
上一個(gè)promise未被成功調(diào)用then方法時(shí),返回的結(jié)果如情形1
const?promise?=?new?Promise((_,?reject)?=>?{
??reject("reject");
});
let?a?=?promise.then(value?=>?{
??console.log(value);
});
上一個(gè)promise被成功調(diào)用then方法時(shí),返回一個(gè)`resolve(undefined)`的promise
const?promise?=?new?Promise((_,?reject)?=>?{
??reject("reject");
});
let?a?=?promise.then(undefined,?value?=>?{
??console.log(value);
});
console.log(a);
上一個(gè)promise被成功調(diào)用then方法,但出現(xiàn)報(bào)錯(cuò),返回一個(gè)`reject('error')`的promise,`error`代指錯(cuò)誤,并非真正的`reject`返回的結(jié)果
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
});
let?p?=?promise.then(value?=>?{
??console.log(value);
??throw?new?Error("fail1");
});
console.log(p);
結(jié)果展示:
給then方法手動(dòng)返回一個(gè)promise,此時(shí)會(huì)覆蓋掉默認(rèn)的行為,返回值為新增的promise
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
});
let?p?=?promise.then(
??()?=>
??new?Promise(resolve?=>?{
????resolve("resolve2");
??})
);
console.log(p);
Promise.prototype.catch()
catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。
const?promise?=?new?Promise((_,?reject)?=>?{
??reject("reject");
});
promise
??.then(value?=>?{
??console.log(value);
})
?//?發(fā)生錯(cuò)誤,或者reject時(shí)執(zhí)行
??.catch(value?=>?{
??console.log(value);
});
如果 Promise 狀態(tài)已經(jīng)變成resolved,再拋出錯(cuò)誤是無效的。
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
??throw?new?Error("fail");
});
promise.then(value?=>?console.log(value));
promise中所有沒有被處理的錯(cuò)誤都會(huì)冒泡到最后一個(gè)catch中
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
});
promise
??.then(value?=>?{
??console.log(value);
??throw?new?Error("fail1");
})
??.then(()?=>?{
??throw?new?Error("fail2");
})
??.catch(value?=>?{
??console.log(value);
});
在上面的代碼中,catch會(huì)優(yōu)先打印打印第一個(gè)錯(cuò)誤,當(dāng)?shù)谝粋€(gè)錯(cuò)誤解決之后(注釋掉就ok),catch里才會(huì)打印第二個(gè)錯(cuò)誤?catch的返回值仍是promise,返回promise的方式和then相似,因此,catch后仍然可以調(diào)用then方法
Promise.prototype.finally()
finally()方法用于指定不管 Promise 對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。finally方法的回調(diào)函數(shù)不接受任何參數(shù),這表明,finally方法里面的操作,應(yīng)該是與狀態(tài)無關(guān)的,不依賴于 Promise 的執(zhí)行結(jié)果。
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
});
promise.finally(()?=>?{
??console.log(11);?//?11
});
finally的本質(zhì)
promise.finally(()?=>?{
??//?do?something
});
//?等同于
promise.then(
??result?=>?{
????//?do?something
????return?result;
??},
??error?=>?{
????//?do?something
????throw?error;
??}
);
finally的返回值為一個(gè)新的和原來的值相似的promise
Promise.resolve()
有時(shí)需要將現(xiàn)有對(duì)象轉(zhuǎn)為 Promise 對(duì)象,Promise.resolve()方法就起到這個(gè)作用,且實(shí)例狀態(tài)為resolve
Promise.resolve('foo')
//?等價(jià)于
new?Promise(resolve?=>?resolve('foo'))
Promise.resolve()方法的參數(shù)分成四種情況
參數(shù)是一個(gè) Promise 實(shí)例
const?promise?=?new?Promise(resolve?=>?{
??resolve("resolve");
});
let?p?=?Promise.resolve(promise);
console.log(p?==?promise);?//?true
參數(shù)是一個(gè)thenable對(duì)象
thenable對(duì)象指的是具有then方法的對(duì)象,Promise.resolve()方法會(huì)將這個(gè)對(duì)象轉(zhuǎn)為 Promise 對(duì)象,然后就立即執(zhí)行thenable對(duì)象的then()方法
//?thenable對(duì)象
let?thenable?=?{
??then:?function(resolve,?reject)?{
????resolve(42);
??}
};
let?p1?=?Promise.resolve(thenable);
p1.then(function?(value)?{
??console.log(value);??//?42
});
上面代碼中,thenable對(duì)象的then()方法執(zhí)行后,對(duì)象p1的狀態(tài)就變?yōu)?code style="margin: 0px 2px;padding: 2px 4px;max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;border-radius: 4px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">resolved,從而立即執(zhí)行最后那個(gè)then()方法指定的回調(diào)函數(shù),輸出42
參數(shù)不是具有then()方法的對(duì)象,或根本就不是對(duì)象
const?p?=?Promise.resolve('Hello');
p.then(function?(s)?{
??console.log(s)?//?Hello
});
不帶有任何參數(shù)
Promise.resolve()方法允許調(diào)用時(shí)不帶參數(shù),直接返回一個(gè)resolved狀態(tài)的 Promise 對(duì)象
Promise.resolve();
//?相當(dāng)于
new?Promise(resolve?=>?resolve(undefined))
Promise.reject()
Promise.reject(reason)方法也會(huì)返回一個(gè)新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected
const?p?=?Promise.reject('出錯(cuò)了');
//?等同于
const?p?=?new?Promise((resolve,?reject)?=>?reject('出錯(cuò)了'))
Promise.all()
Promise.all()方法用于將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例
const?p?=?Promise.all([p1,?p2,?p3]);
面代碼中,Promise.all()方法接受一個(gè)數(shù)組作為參數(shù),p1、p2、p3都是 Promise 實(shí)例,如果不是,就會(huì)調(diào)用Promise.resolve方法,將參數(shù)轉(zhuǎn)為 Promise 實(shí)例,再進(jìn)一步處理。另外,Promise.all()方法的參數(shù)可以不是數(shù)組,但必須具有 Iterator 接口,且返回的每個(gè)成員都是 Promise 實(shí)例。p的狀態(tài)由p1、p2、p3決定,分成兩種情況。
只有 p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled,此時(shí)p1、p2、p3的返回值組成一個(gè)數(shù)組,傳遞給p的回調(diào)函數(shù)。只要 p1、p2、p3之中有一個(gè)被rejected,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。
let?promise?=?Promise.all([1,?2,?3]);
promise.then(value?=>?{
??console.log(value);?//?[1,2,3]
});
console.log(promise);
情形一promise結(jié)果:
let?p2?=?Promise.reject(2);
let?promise?=?Promise.all([1,?p2,?3]);
promise
??.then(value?=>?{
??console.log(value);
})
??.catch(err?=>?{
??console.log(err);?//?2
});
console.log(promise);
情形二promise結(jié)果:
如果作為參數(shù)的 Promise 實(shí)例,自己定義了catch方法,那么它一旦被rejected,并不會(huì)觸發(fā)Promise.all()的catch方法
const?p1?=?new?Promise(resolve?=>?{
??resolve("hello");
})
.then(result?=>?result)
.catch(e?=>?e);
const?p2?=?new?Promise(()?=>?{
??throw?new?Error("報(bào)錯(cuò)了");
})
.then(result?=>?result)
.catch(e?=>?e);?//?p2實(shí)際上是catch返回的promise實(shí)例
Promise.all([p1,?p2])
??.then(result?=>?console.log(result))
??.catch(e?=>?console.log(e));
Promise.race()
Promise.race()方法同樣是將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。Promise.race()方法的參數(shù)與Promise.all()方法一樣,如果不是 Promise 實(shí)例,就會(huì)先調(diào)用下面講到的Promise.resolve()方法,將參數(shù)轉(zhuǎn)為 Promise 實(shí)例,再進(jìn)一步處理。
const?p?=?Promise.race([p1,?p2,?p3]);
上面代碼中,只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。
Promise.allSettled()
Promise.allSettled()方法接受一組 Promise 實(shí)例作為參數(shù),包裝成一個(gè)新的 Promise 實(shí)例。只有等到所有這些參數(shù)實(shí)例都返回結(jié)果,不管是fulfilled還是rejected,包裝實(shí)例才會(huì)結(jié)束,參數(shù)與Promise.all()方法一樣
let?p2?=?Promise.reject(2);
let?promise?=?Promise.allSettled([1,?p2,?3]);
promise.then(value?=>?{
??console.log(value);?//?[{status:?"fulfilled",?value:?1},{status:?"rejected",?reason:?2},{status:?"fulfilled",?value:?3}]
});
console.log(promise);
Promise.allSettled()的返回的promise實(shí)例狀態(tài)只可能變成resolved,它的監(jiān)聽函數(shù)收到的參數(shù)是一個(gè)數(shù)組,該數(shù)組的每個(gè)成員都是一個(gè)對(duì)象,每一個(gè)對(duì)象都有status屬性,該屬性的值只可能是字符串fulfilled或字符串rejected。fulfilled時(shí),對(duì)象有value屬性,rejected時(shí)有reason屬性,對(duì)應(yīng)兩種狀態(tài)的返回值。
Promise.any()
該方法接受一組 Promise 實(shí)例作為參數(shù),包裝成一個(gè)新的 Promise 實(shí)例返回。只要參數(shù)實(shí)例有一個(gè)變成fulfilled狀態(tài),包裝實(shí)例就會(huì)變成fulfilled狀態(tài);如果所有參數(shù)實(shí)例都變成rejected狀態(tài),包裝實(shí)例就會(huì)變成rejected狀態(tài)。Promise.any()跟Promise.race()方法很像,只有一點(diǎn)不同,就是不會(huì)因?yàn)槟硞€(gè) Promise 變成rejected狀態(tài)而結(jié)束。
let?p1?=?Promise.reject(1);
let?p2?=?Promise.reject(2);
let?promise?=?Promise.any([p1,?p2,?3]);
promise.then(value?=>?{
??console.log(value);?//?3
});
console.log(promise);
當(dāng)所有的實(shí)例返回的狀態(tài)都是rejected時(shí),Promise.any()會(huì)返回一個(gè)的實(shí)例狀態(tài)才為rejected
let?p1?=?Promise.reject(1);
let?p2?=?Promise.reject(2);
let?promise?=?Promise.any([p1,?p2]);
promise
??.then(value?=>?{
??console.log(value);
})
??.catch(value?=>?{
??console.log(value);?//?AggregateError:?All?promises?were?rejected
});
console.log(promise);
Promise.try()
實(shí)際開發(fā)中,經(jīng)常遇到一種情況:不知道或者不想?yún)^(qū)分,函數(shù)f是同步函數(shù)還是異步操作,但是想用 Promise 來處理它。因?yàn)檫@樣就可以不管f是否包含異步操作,都用then方法指定下一步流程,用catch方法處理f拋出的錯(cuò)誤。一般就會(huì)采用下面的寫法。
const?f?=?()?=>?console.log('now');
Promise.resolve().then(f);
console.log('next');
//?next
//?now
上面的寫法有一個(gè)缺點(diǎn),就是如果f是同步函數(shù),那么它會(huì)在本輪事件循環(huán)的末尾執(zhí)行。鑒于這是一個(gè)很常見的需求,所以現(xiàn)在有一個(gè)提案,提供Promise.try方法替代上面的寫法。
const?f?=?()?=>?console.log('now');
Promise.try(f);
console.log('next');
//?now
//?next
帶你寫出符合 Promise/A+ 規(guī)范 Promise 的源碼