【JS】881- 我終于弄懂了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="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">resolved和從pending變?yōu)?code style="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;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="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">resolved,將異步操作的結(jié)果,作為參數(shù)傳遞出去reject——將Promise對(duì)象的狀態(tài)從pending變?yōu)?code style="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;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="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;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="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;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="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;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 的源碼

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 100+ 篇原創(chuàng)文章
