解決非同步程式設計的方法:promise與await
promise是什麼?
Promise,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。簡單來說,promise的作用就是將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。
promise的特點
① 物件的狀態不受外界影響:promise非同步操作有三種狀態:進行中,已成功,已失敗。只有非同步操作才能改變這個狀態。 ②一變則不變:promise狀態一旦改變,就不會再發生變化,promise物件改變的兩種可能,進行中—>已成功,進行中—>已失敗。
promise的基本用法
promise物件是一個建構函式,用來生成promise例項
例子:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 非同步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
其中接受的引數是resolve和reject兩個函式
resolve的作用:將promise物件的狀態由進行中—>已完成。並將非同步操作的結果作為引數傳遞出去
rejected的作用:將promise物件的狀態由進行中—>已失敗,並將非同步失敗的原因作為引數傳遞出去。
注意:呼叫resolve或reject並不會終結promise的引數函式的執行
例子:
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
上面程式碼呼叫了resolve(1)以後,後面的console(2)還是會執行,並且會首先打印出來。這是因為立即resolved的promise是在本輪事件迴圈的末尾執行,總是晚於本輪迴圈的同步任務。
then的用法
promise例項生成後,用then方法分別指定resolved狀態和rejucted狀態的回撥函式。
例子:
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受兩個回撥函式作為引數,第一個回撥函式是當promise物件狀態是resolve(已完成)的時候呼叫,第二個回撥函式(可選)是當promise物件狀態是reject(已失敗)的時候呼叫。
如例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
// 結果是done
鏈式的then用法
then方法返回的是一個新的Promise例項(注意,不是原來那個Promise例項)。因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法
例子
getjsON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
上面程式碼中,第一個then方法指定的回撥函式,返回的是另一個Promise物件。這時,第二個then方法指定的回撥函式,就會等待這個新的Promise物件狀態發生變化。如果變為resolved,就呼叫funcA,如果狀態變為rejected,就呼叫funcB。
catch方法
promise物件中,如果非同步操作丟擲錯誤,狀態就會變為rejected,就會呼叫catch方法指定的回撥函式處理這個錯誤,另外,then方法指定的回撥函式,如果執行中丟擲錯誤也會被catch方法捕獲。
例子:
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同於
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
promise物件的錯誤具有“冒泡”性質,會一直向後傳,直到被捕獲,也就是說,會跳過中間的then函式
例子:
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 處理前面三個Promise產生的錯誤
});
finally方法
finally方法用於指定不管promise物件最後狀態如何,都會執行的操作。
例子
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
例子是伺服器使用promise處理請求,然後使用finally()方法關掉伺服器。
promise.all()方法
promise.all方法用於將多個promise例項,包裝成一個新的promise例項。
比如:constp = Promise.all([p1, p2, p3]);
Promise.all方法,接受的是一個數組作為引數,其中的元素都是promise例項,如果不是,則會自動將引數轉變為promie例項。
p的狀態是有它的數組裡面的元素決定的,分兩種狀態(用上面舉例)
①只有p1 p2 p3的狀態都變成fulfilled(已完成)的狀態才會變成fulfilled(已完成),此時p1 p2 p3的返回值組成一個數組,傳遞給p的回撥函式。
②只有p1 p2 p3之中,有一個被rejucted(未完成),p的狀態就會變成rejected(未完成),此時第一個被reject的例項的返回值,會傳遞給p的回撥函式。
例子:
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報錯了enter code here
promise.race()方法
Promise.race方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項
const p = Promise.race([p1, p2, p3]);
如果p1 p2 p3不是promise例項,也會自動轉變成promise例項
與promise.all不同的是,上面程式碼中,只用p1、p2、p3之中有一個例項率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給p的回撥函式。
async函式是什麼
async的引入,使得非同步操作變得更加方便,那async函式是什麼,其實它是Generator函式的語法糖。使非同步函式、回撥函式在語法上看上去更像同步函式。Generator這裡就不介紹了。我們直接來學習async
async的基本用法
async返回值是一個promise物件,因此可以使用then方法添加回調函式,當函式執行的時候,一旦遇到await就會先返回,等到非同步操作完成,再接著執行函式體內後面的內容。
例子:
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
async函式的使用形式
//函式宣告
async function foo(){}
//函式表示式
const foo = async function(){};
//物件的方法
let obj = {async foo(){}};
obj.foo().then(...)
//class的方法
class Storage{
consttuctor(){
this.cachePromise=caches.open('avatars');
}
async getAvatar(name){
const cache = await this.cachePromise;
return cache.match('/avatars/${name}.jpg')};
}
}
const storage =new Storage();
storage.getAvatar('jake').then(....);
}
}
const storage =new Storage();
storage.getAvatar('jake').then(...)
//箭頭函式
const foo =async()=>{};
async 函式內部return語句返回的值,會成為then方法呼叫函式的引數。
例子:
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
await 命令
正常情況下,await命令後面跟著的是一個promise物件,如果不是會自動轉化為promise物件
例子:
async function f(){
return await 123;
}
f().then(v =>console.log(v))
//123
當一個await語句後面的promise變為reject,那麼整個函式都會中斷執行。
例子:
async function f() {
await Promise.reject('出錯了');
await Promise.resolve('hello world'); // 不會執行
}
資源搜尋網站大全 https://www.renrenfan.com.cn
錯誤處理
如果await 後面的非同步操作有錯,那麼等同於async函式返回的promis物件被reject (上文講promise物件的時候有提到過,冒泡性質) 例子:
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出錯了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯了
使用try ....catch程式碼塊課防止出錯。
例子:
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出錯了');
});
} catch(e) {
}
return await('hello world');
}
也可以將多個await命令都放在try..catch結構中
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
注意點
①await命令只能用在async函式中,用在普通函式中會報錯。