由於寫文章時太晚了,有點困,複製錯代碼了,已改正。html
上一篇博客咱們在現實使用和麪試角度講解了Promise(原文可參考《面向面試題和實際使用談promise》),可是Promise 的方式雖然解決了 callback hell,可是這種方式充滿了 Promise的 then()
方法,若是處理流程複雜的話,整段代碼將充滿 then
,代碼流程不能很好的表示執行流程。node
在es6中,咱們可使用Generator函數控制流程,以下面這段代碼:ios
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}複製代碼
咱們能夠根據不斷地調用Generator對象的next()
方法來控制函數的流程。可是這樣彷彿不是那麼的語義化。所以,在ES6中封裝了Generator函數的語法糖async函數,可是將其定義在了es7中。ES7定義出的async
函數,終於讓 JavaScript 對於異步操做有了終極解決方案。Async
函數是 Generator函數的語法糖。使用 關鍵字 Async
來表示,在函數內部使用 await來表示異步。相較於 Generator,Async函數的改進在於下面幾點:Generator 函數的執行必須依靠執行器,而 Async()
函數自帶執行器,調用方式跟普通函數的調用同樣。Async
和 await相較於 *
和 yield
更加語義化。async
函數返回值是 Promise 對象,比 Generator函數返回的 Iterator 對象方便,能夠直接使用 then()
方法進行調用。程序員
那麼,咱們經過一段小小的代碼來講明async/await函數的用法:es6
未使用async/await的定時函數:面試
fn = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
}
const Fn = () =>{
fn().then((res) => {
console.log(res)
})
}
Fn()
console.log(2)複製代碼
我相信能看到這裏的各位程序員大佬應該都知道這段代碼的輸出情況:先打印2,2s以後打印出1。mongodb
使用async/await的定時函數:數據庫
fn = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
}
const Fn = async () => {
await fn().then((res) => {
console.log(res)
})
console.log(2)
}
Fn()
複製代碼
這一段函數的輸出情況是:2s後打印1,而後打印2。axios
那麼,why?promise
咱們在字面上理解這兩個單詞async和await:async的意思是異步,async用於定義一個異步函數,該函數返回一個Promise。;await的意思是等待,Promise是一個承諾,await也是一個承諾。Promise的承諾是將返回值輸出到then的回掉函數裏面,不管是成功仍是失敗。await的承諾是不管颳風仍是下雨,我都會等你完成在作其餘的步驟。所以,在上面的運用了async/await的代碼中,會等待fn徹底運行完成而且異步的回調完成對返回值的處理以後在開始進行下一步操做的。其原理是將異步函數轉變爲同步操做。
在上週的工做中,我在一段基於node完成的爬蟲操做中屢次運用async/await來控制程序的執行流程:
//僞代碼
let axiosArr = [];
for (let i = 0, len = arr.length; i < len; i++) {
let params = qs.stringify({
'param': arr[i].index,
})
axiosArr.push(axios.post(url, params, {
headers
}))
}
/*
*上面的循環是循環抓取2345條數據,平均每一個數據要訪問16個接口
*用axios.all同時詢問,當返回結束後將返回值處理
*而後將返回值存儲到mongodb數據庫中
*/
await axios.all(axiosArr).then(
axios.spread(function () {
for (let i = 0, len = arguments.length; i < len; i++) {
let str = `${unescape(arguments[i].data.replace(/\\u/g, '%u'))}`;
str = basics.subStr(basics.deletN(basics.deletS(basics.cutStr(str))));
concentArr[i].concent = str
}
mg.mongodbMain({
name: obj.name,
alias: obj.alias,
type: type,
url: obj.url,
drugsConcent: concentArr
})
}))複製代碼
其實操做就這麼點,你們看一下代碼都會懂。可是問題是,當我不使用async/await時,會產生的狀況是會先訪問2000+個數據,不斷訪問其16個接口,可是因爲promise的then的回調函數爲異步的,會掛起,而不是直接將數據存到數據庫中。這貌似和咱們預想的不同啊。所以,我在這裏使用了async/await函數,使用同步處理異步操做,將promise同步化,當axios.all訪問完成這每一條數據的16個接口後,直接將數據存儲到數據庫中,而後纔會走到循環的下一層,依舊是訪問下一條數據的16個接口。
咱們說過了async
函數返回值是 Promise 對象。
const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
await delay(1000);
await delay(2000);
await delay(3000);
return 'done';
}
f().then(v => console.log(v));// 6s以後打印'done'複製代碼
那麼其內部一旦拋出異常,則會致使返回的 Promise 對象狀態變爲 reject
狀態。拋出的錯誤而會被 catch
方法回調函數接收到。
async function e(){
throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));//拋出的錯誤會被catch捕捉到複製代碼
而且,async有一個和promise.all類似的特性,就是內部一點有一個await函數報錯,後續的就再也不執行了
let fn1 = ()=>{
return new Promise((resolve,reject) => {
setTimeout(()=>{
reject('故意拋出錯誤');
},500);
});
}
let fn2 = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},500);
});
}
let getList = async ()=>{
let a = await fn1();
let b = await fn2();
return {first: a,second:b};
}
getList().then(result=> {
console.log(result);
}).catch(err=> {
console.log(err);// 因爲fn1的報錯,async的狀態直接變成了rejected
});複製代碼
當Promise出現的時候,咱們彷彿看到了回調地獄的滅亡。當Async/Await出現時,異步終於不是一件困難的事情。