因為JavaScript屬於同步的語言,一次只能作一件事情,遇到非同步的事件就會把該事件挪到最後執行。
console.log("Start!");
setTimeout(() => { console.log("非同步事件") },1000)
console.log("End!")
//
Start!
End!
非同步事件
Ajax也一樣屬非同步行為,如果要確保擷取到遠端資料後,並且針對遠端資料作處理的話,那了解promise的運作就非常重要。以下例子為Promise base的Ajax函式庫axios為例子:
let data = [];
axios.get('https://randomuser.me/api/').then((res) => {
data = res
console.log(data) // random user data
})
console.log(res) // []
Promise object的構造器語法如下:
let promise = new Promise(function(resolve, reject) {
// executor
});
傳遞給new Promise的函數被稱為executor。當new Promise被創建,executor會自動運行。
當executor獲得了結果,會執行以下兩個callback:
new Promise構造器返回的promise對象具有以下的內部屬性:
舉一個promise構造器和一個簡單的executor函數:
let promise = new Promise(function(resolve, reject) {
// 當new promise時,自動執行此函數
// 1秒後執行成功,帶有value "done"
setTimeout(() => resolve("done"), 1000);
});
經過一秒的處理後,executor調用resolve('done')來產生結果,改變promise object的狀態:
reject的例子:
let promise = new Promise(function(resolve, reject) {
// 1 秒后发出工作已经被完成的信号,并带有 error
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
executor只能調用一個resolve或是一個reject。所以再對resolve或是reject的調用都會被忽略。
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // 被忽略
setTimeout(() => resolve("…")); // 被忽略
});
盡量以Error object調用reject
Resolve/reject可以立即進行(無非同步)
實際上executor通常是進行某些非同步操作,並在一段時間後調用resolve/reject,但這不是必須的
let promise = new Promise(function(resolve, reject) {
// 不花时间去做这项工作
resolve(123); // 立即给出结果:123
});
state與result都是內部屬性,無法直接獲取,但我們可以使用.then/.catch/.finally等方法。
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
.then接收兩個函數,第一個函數接收promise resolve後的結果,第二個則接收promise reject的結果。
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(
result => alert(result), // 1秒後顯示 "done!"
error => alert(error) // 不運行
);
reject的情況下:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
promise.then(
result => alert(result), // 不運行
error => alert(error) // 1秒後顯示 "Error: Whoops!"
);
如果只對成功完成感興趣,那也可以只提供一個函數。
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1秒後顯示 "done!"
如果只對error感興趣,可以使用null作為第一個參數,.then(null,errorHandleFunction),或者使用.catch:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f)跟promise.then(null, f)是一樣的
promise.catch(alert); // 1秒後顯示 "Error: Whoops!"
finally中的callback function是在promise執行resolve或reject後執行,是一個很好執行清理(clean up)或是處理程序(handler),例如無論執行的結果如何都可以在finally中把loading indicator關掉。
new Promise((resolve, reject) => {
// 做一些處理後調用resolve or reject
})
// promise為settled,不管成功與否
.finally(() => stop loading indicator)
// 所以在finally中的處理,都會在我們處理成功/錯誤結果之前
.then(result => show result, err => show error)
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => alert("Promise ready"))
.then(result => alert(result));
或是一起傳給catch
new Promise((resolve, reject) => {
throw new Error("error");
})
.finally(() => alert("Promise ready"))
.catch(err => alert(err));
可以在.then()中回傳一個promise,那下一個.then()就會等到回傳的promise調用resolve或reject:
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}).then((result) => {
console.log(result);
return new Promise((resolve, reject)=> {
setTimeout(() => resolve(2), 2000)
})
}).then((result) => {
console.log(result);
})
通常會在Promise chain中的末端利用.catch()進行錯誤處理,所以可能會在多個.then()後才出現,如果整個promise chain都沒有報錯,那.catch()就不會被執行。
直接看一個簡單的例子:
function testError() {
return new Promise((resolve, reject) => {
nonExistMethod();
})
}
testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })
// error catch successfully! ReferenceError: nonExistMethod is not defined
為什麼沒有執行reject,error也會被.catch捕抓到呢? 原因是因為Promise內部有一層try..catch所包住,而且catch的部分預設使用reject function。
那如果error是出現在Promise外面呢?
function testError() {
nonExistMethod();
return new Promise((resolve, reject) => {
resolve("result!")
})
}
testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })
這樣的情況下,promise chain的.catch是沒有辦法捕捉到error的,必須自己利用try..catch來補捉:
function testError() {
nonExistMethod();
return new Promise((resolve, reject) => {
resolve("result!")
})
}
try {
testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })
} catch(error) {
console.log("handle by outer try-catch")
}
最後介紹Promise的其他方法:
說明這些方法前,先定義一個promise函數,可以傳入兩個參數:
function ownPromise(count, time = 1000) {
return new Promise((resolve, reject) => {
setTimeout(() => {
count? resolve(`第${count}次成功!`) : reject("失敗")
},time)
})
}
以數組的形式傳入多個promise函數,結果一樣以數組的形式回傳。
Promise.all([ownPromise(1), ownPromise(2), ownPromise(3,4000)]).then((res) => { console.log(res) })
// ["第1次成功!", "第2次成功!", "第3次成功!"]
以數組的形式傳入多個promise函數,與Promise.all不同的是只會回傳單一結果,也就是第一個完成的。
Promise.race([ownPromise(1), ownPromise(2), ownPromise(3,4000)]).then((res) => { console.log(res) })
// 第1次成功!
直接看例子會比較快明白:
let result = Promise.resolve("result");
result.then(res => { console.log(res) }) // result
let rejectResult = Promise.reject("error");
rejectResult.catch(rej => { console.log(rej) }) // error