由于写文章时太晚了,有点困,复制错代码了,已改正。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出现时,异步终于不是一件困难的事情。