Promise
是JavaScript中的一個核心概念倘感,初學(xué)JavaScript咙咽,對Promise
的概念和用法都比較模糊,這里做一個總結(jié)蜡豹。
首先Promise是用來執(zhí)行異步代碼的,用來替代回調(diào)函數(shù)余素。在講回調(diào)函數(shù)之前,可以先看下什么是同步操作威根,什么是異步操作视乐。
同步操作
正常代碼是同步執(zhí)行的,執(zhí)行一條命令需要等它完成之后才會執(zhí)行下一條命令留美。例如讀取一個文件的內(nèi)容伸刃,
// Node.js program to demonstrate the
// fs.readFileSync() method
// Include fs module
const fs = require('fs');
// Calling the readFileSync() method
// to read 'input.txt' file
const data = fs.readFileSync('./input.txt',
{encoding:'utf8', flag:'r'});
// Display the file data
console.log(data);
執(zhí)行完之后立刻就可以知道文件的內(nèi)容,但這種方式耗時比較久景图,在等待執(zhí)行完成的過程時候碉哑,不能執(zhí)行其他操作,這樣就很容易阻塞到UI線程妆毕。
異步操作
與同步方式不同贮尖,異步操作在調(diào)用某個函數(shù)后立刻返回笛粘,同時使用callback函數(shù)湿硝,在異步操作完成的時候自動調(diào)用。讀文件用異步來操作可以表示為:
readFile("./input.txt", (error, result) => {
// This callback will be called when the task is done, with the
// final `error` or `result`. Any operation dependent on the
// result must be defined within this callback.
console.log(result);
});
// Code here is immediately executed after the `readFile` request
// is fired. It does not wait for the callback to be called, hence
// making `readFile` "asynchronous".
這種異步操作有兩個缺點序六,一個是多層的callback會比較晦澀難懂蚤吹,不容易維護;第二個是出錯處理很麻煩繁涂,因為實際在執(zhí)行回調(diào)函數(shù)的時候已經(jīng)沒有之前同步執(zhí)行的堆棧,沒有辦法回溯到可以處理異常的代碼塊秉沼。
Promise
Promise
用來表示一次異步操作的未來結(jié)果矿酵,既然是表示未來的結(jié)果,那就需要通過這個Promise
可以知道異步操作是否成功敞咧,如果成功辜腺,返回值是什么?如果失敗测砂,失敗的異常是什么百匆。同時因為是異步操作,Promise
可以注冊回調(diào)函數(shù)胧华,需要注意的是矩动,注冊的回調(diào)函數(shù)最多只能被調(diào)用一次释漆;
創(chuàng)建Promise
- 使用
Promise
的構(gòu)造函數(shù)來創(chuàng)建,用法如下:
創(chuàng)建一個Promise
的語法是
new Promise(executor)
其中executor
是一個函數(shù)示姿,接受兩個參數(shù)resolve
和reject
逊笆。resolve
和reject
都是函數(shù),并且接受一個任意類型的輸入?yún)?shù)子檀。
其中resolve
函數(shù)解決或兌現(xiàn)返回的Promise
,或者調(diào)用reject
函數(shù)拒絕返回的Promise
.
上面這個例子中的執(zhí)行過程如下:
-
new Promise
會構(gòu)造一個Promise
對象褂痰,同時會產(chǎn)生兩個函數(shù)對象缩歪,分別是resolve
和reject
,這兩個函數(shù)對象和這個Promise
對象綁定在一起匪蝙。 -
new Promise
的參數(shù)是一個函數(shù)executor
逛球,這個函數(shù)用來封裝一些操作,這些操作會通過異步的方式執(zhí)行需忿。同時這個函數(shù)接受兩參數(shù)屋厘,分別是resolve
和reject
.executor
在創(chuàng)建Promise
后立刻執(zhí)行,同時把resolve
和reject
的對象作為參數(shù)汗洒。 - 這個
Promise
的狀態(tài)是通過調(diào)用resolve
和reject
的調(diào)用來改變的溢谤。
- 如果
resolve
先被調(diào)用,那么Promise
就被解決或者兌現(xiàn)世杀,同時傳給resovle
的參數(shù)會被認為Promise
對象對應(yīng)的結(jié)果瞻坝。 - 如果
reject
先被調(diào)用,那么Promise
則被拒絕所刀,同時傳給reject
的參數(shù)也會被認為是Promise
對象對應(yīng)的異常浮创。
以下例子來源于MDN:
const myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR or an HTML API.
setTimeout(() => {
resolve("Success!"); // Yay! Everything went well!
}, 250);
});
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log(`Yay! ${successMessage}`);
});
以上例子中就是通過resolve
來把"Success"信息傳給通過myFirstPromise.then
注冊的回調(diào)函數(shù),同時myFirstPromise
也會變?yōu)?code>fulfill的狀態(tài)溜族。
- 使用
then()
函數(shù)來創(chuàng)建
then()
可以創(chuàng)建并返回一個新的Promise
,例如
function getJSON(url){
return fetch(url).then(response => response.json());
}
fetch(url)
返回是一個Promise P1
劣像,P1
對應(yīng)的實際結(jié)果是一個Response
對象摧玫。Response
對象的 json()
方法返回一個新的Promise P2
,那么P1
被解決為P2
屋群。當(dāng)P2
兌現(xiàn)時(fulfill)坏挠,P1
也會用相同的值來fulfill.
還是用剛才讀文件的例子來說明,如果用Promise實現(xiàn)的話就是以下方式:
const readFilePromise = (path) =>
new Promise((resolve, reject) => {
readFile(path, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
readFilePromise("./input.txt")
.then((result) => console.log(result))
.catch((error) => console.error("Failed to read data"));
使用Promise
如果使用Promise異步对竣,假設(shè)有一個函數(shù)readFilePromise
榜配,那么上面的代碼就變成:
readFilePromise("./input.txt").then(fileContent => {
// 該函數(shù)為callback函數(shù),在文件內(nèi)容被讀出的時候調(diào)用临燃,接受的參數(shù)為
// 文件的內(nèi)容烙心。注意該函數(shù)只會被調(diào)用一次。
console.log(fileContent);
});
注意readFilePromise
返回的是一個Promise對象爪瓜,Promise
對象有then()
的方法痘昌,可以用來注冊回調(diào)函數(shù)炬转。該回調(diào)函數(shù)在Promise兌現(xiàn)后調(diào)用,調(diào)用的時候傳入的參數(shù)為該Promise
的實際結(jié)果驻啤,在此處就是文件的實際內(nèi)容荐吵;
如果讀取文件失敗赊瞬,怎么處理:
readFilePromise("./input.txt").then(fileContent => {
// 該函數(shù)為callback函數(shù)巧涧,在文件內(nèi)容被讀出的時候調(diào)用遥倦,接受的參數(shù)為
// 文件的內(nèi)容。注意該函數(shù)只會被調(diào)用一次缩筛。
console.log(fileContent);
}).catch(() => console.log("error"));
如果要等待Promise
的完成堡称,可以使用
await yourPromise;
Promise的狀態(tài)
一個Promise
可以有三個狀態(tài),分別是fulfill
却紧,reject
和pending
晓殊。剛創(chuàng)立的時候,Promise
的狀態(tài)為pending
挺物,一旦被fulfill
或者reject
识藤,則該Promise
為settle,且狀態(tài)不會改變痴昧。
一個Promise
代表了異步操作的結(jié)果,如果異步代碼正常結(jié)束舌镶,這個結(jié)果就是代碼的正常返回值豪娜,同時這個結(jié)果會作為參數(shù)傳遞給then()的第一個參數(shù)注冊的函數(shù);如果代碼執(zhí)行異常否灾,那這個結(jié)果就是一個Error對象或者某個其他值鸣奔,這個結(jié)果會作為參數(shù)傳遞給catch()注冊的或者then()的第二個參數(shù)注冊的回調(diào)函數(shù)。
Promise Chain
先看一個例子:
fetch(theURL) // task 1, return Promise P1
.then(callback1) // task 2, return Promise P2
.then(callback2); // task 3, return Promise P3
callback1是當(dāng)P1
兌現(xiàn)的時候調(diào)用扣汪,并且把P1
的結(jié)果作為參數(shù)傳給callback1; callback1
必須返回一個新的Promise P2
崭别,并把P2
兌現(xiàn)的結(jié)果作為輸入?yún)?shù)送給callback2
。
舉一個具體的例子:
function callback1(response){
let p4 = response.json();
return p4
}
function callback2(profile){
displayUserProfile(profile);
}
let p1 = fetch("/api/user/profile");
let p2 = p1.then(callback1);
let p3 = p2.then(callback2);
Reference
MDN Promise
參數(shù)結(jié)構(gòu)
JavaScript權(quán)威指南第7版