async和await對promise非同步方案的改進,以及使用注意事項
async、await相比原生promise的有優勢:
1.更加簡潔,await一個promise即可,那麼會自動返回這個promise的resolve值,無需在then函式的回撥中手動取值,徹底解決了回撥
//Promise方式 function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve('done!'), 1000) }) promise.then((res) => { console.log('object :', res); }) } f()
//async、await
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('done!'), 1000)
})
let result = await promise // 直到promise返回一個resolve值(*)
console.log('object :', result);
}
f()
2.避免了then鏈式呼叫的,沒有設定失敗回撥而導致異常被丟擲到then鏈的末端,而導致被忽略,向下面程式碼一樣,如果then沒有設定失敗回撥,那麼預設的失敗回撥會將異常拋給下一個then函式的失敗回撥,如果末端沒有一個catch函式。那麼異常就會丟失,問題是如果catch程式碼中的異常處理程式碼又有異常丟擲呢,那麼這個異常只能在下一個then中捕獲,這是容易被忽略的錯誤
//promise
let p = new Promise((resolve, reject) => {
reject()
})
p.then().then().catch()
async任意一個await出現了異常,await會自動丟擲reject,並且程式會被停止,異常統一在try-catch塊可以捕獲而不會出現捕獲鏈無限延長的問題
//async、await async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => reject('done!'), 1000) }) try { let result = await promise // 直到promise返回一個resolve值(*) } catch (error) { console.log('object :', error); } } f()
3.then鏈式流中,資料訪問不能很自然的跨層訪問
MongoClient.connect(url + db_name).then(db=> {
return db.collection('blogs');
}).then(coll=> {
return coll.find().toArray();
}).then(blogs=> {
console.log(blogs.length);
}).catch(err=> {
console.log(err);
先連線資料庫MongoClient.connect()返回一個Promise,然後在then()方法裡獲得資料庫物件db,然後再獲取到coll物件再返回。在下一個then()方法獲得coll物件,然後進行查詢,查詢結果返回,逐層呼叫then()方法,形成一個Promise鏈。
這時候我們有一個需求,第三個then(blogs => {})中我們只能獲取到查詢的結果blogs,而不能使用上面的db物件和coll物件。這個時候,如果要打印出blogs列表後,要關閉資料庫db.close()怎麼辦?
兩種處理方式:
1.使用then巢狀
MongoClient.connect(url + db_name).then(db=> {
let coll = db.collection('blogs');
coll.find().toArray().then(blogs=> {
console.log(blogs.length);
db.close();
}).catch(err=> {
console.log(err);
});
}).catch(err=> {
console.log(err);
問題:
這會打斷Promise鏈,導致then的回撥地獄,而且導致在每一個then中都需要手動捕獲異常,因為then沒成鏈,不能自然傳遞異常
2.每個then()方法裡都將db傳過來
MongoClient.connect(url + db_name).then(db=> {
return {db:db,coll:db.collection('blogs')};
}).then(result=> {
return {db:result.db,blogs:result.coll.find().toArray()};
}).then(result=> {
return result.blogs.then(blogs=> { //注意這裡,result.coll.find().toArray()返回的是一個Promise,因此這裡需要再解析一層
return {db:result.db,blogs:blogs}
})
}).then(result=> {
console.log(result.blogs.length);
result.db.close();
}).catch(err=> {
console.log(err);
問題:
我們在then方法中,都將db和其他結果合併成一個物件,特別需要注意的是,如果傳遞的值含有promise,那麼還需要多做一層解析,也就是需要單獨給予一個then函式進行處理,況且每次都要傳遞一個多餘的物件(對於到達實際使用地方這段路徑,這個物件是不需要使用的)
async、await方案:
let getBlogs = async function(){
let db = await MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = await coll.find().toArray();
db.close();
return blogs;
};
getBlogs().then(result=> {
console.log(result.length);
}).catch(err=> {
console.log(err);
這裡await解決了then鏈的問題,使得then跨層訪問的問題從根本上被解決了,因為await的promise的resolve值被置於同一個作用域,可以隨意訪問
4.使得原本非同步非阻塞的表達方式,變成了更加同步阻塞的程式碼,這得益於ES6中生成器和迭代器,賦予js函式的魔力,本質上,async、await是生成器和迭代器以及Promise結合的語法糖,它使得promise之前設計缺陷被更好地修正,目前看來,async、await,是非同步的終極解決方案之一
async function basicDemo() {
let result = await Math.random();
console.log(result);
}
basicDemo();
await由於自動返回了resolve的值,無需then,我們甚至沒有感知到非同步的存在,他將非同步從語法層面上進行了同步化
async、await使用注意事項:
1.await雖然可以像Promise.resolve作用域很多型別的資料,但它的主要意圖是用來等待Promise物件被resolved,如果await是非promise值,那麼會被立即執行
2.async函式將返回值自動包裝成一個promise,就像Promise.resolve一致的行為
3.await必須在async函式上下文,也就是如下程式碼的區別
// 正常 for 迴圈
async function forDemo() {
let arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i ++) {
await arr[i];
}
}
forDemo();//正常輸出
// 因為想要炫技把 for迴圈寫成下面這樣
async function forBugDemo() {
let arr = [1, 2, 3, 4, 5];
arr.forEach(item => {
await item;
});
}
forBugDemo();// Uncaught SyntaxError: Unexpected identifier
4.小心自己的並行處理,也許不小心就將ajax的併發請求發成了阻塞式同步的操作,理解這句話的核心是: await若等待的是promise,那麼程式就會在此處等到promise的resolved,然後繼續往下,看下面例子,這裡第一個sleep會等待自身resolved完成才會往下,如果我們可以讓這些函式並行,同時保持await的特性,那麼效率會大大提高
function sleep(second) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('request done! ' + Math.random());
}, second);
})
}
async function bugDemo() {
await sleep(1000);
await sleep(1000);
await sleep(1000);
console.log('clear the loading~');
}
bugDemo();
正確的姿勢是:
async function correctDemo() {
let p1 = sleep(1000);
let p2 = sleep(1000);
let p3 = sleep(1000);
await Promise.all([p1, p2, p3]);//這裡單獨await每一個promise也是一樣的效果
console.log('clear the loading~');
}
correctDemo();// clear the loading~