异步编程下篇

16-4 asyncawait (次重点)

上一节咱们学习了如何利用生成器实现异步操作。在生成器中,利用yield将异步操作挂起,外部 通过执行器让生成器的代码继续执行。这样,在生成器中,可以将异步的操作做成同步的效果, 实现了异步代码的简化。不过,这种方式需要编写外部的执行器,而执行器的代码写起来一点也 不简单。当然也可以使用一些插件,比如co模块来简化执行器的编写。

在ES7中,加入了 async函数来处理异步。它实际上只是生成器的一种语法糖而已,简化了外部 执行器的代码,同时利用await替代yield, async替代生成器的*号。下面还是来看个例子:

async function delay(){

await new Promise((resolve) => {setTimeout(()=>{resolve()},2000)}); console.log("go on);

} delay();

这个例子我们之前用生成器也写过,其中把生成器的(*)号被换成了async。async关键字必须写在 function的前面。如果是箭头函数,则写在参数的前面:

const delay = async () => {}

在函数中,第一句用了 await。它替代了之前的yield。后面同样需要跟上一个Promise对象。接下 来的打印语句会在上面的异步操作完成后执行。外部调用时就和正常的函数调用一样,但它的实 现原理和生成器是类似的。因为有了 async关键字,所以它的外部一定会有相应的执行器来执行 它,并在异步操作完成后执行回调函数。只不过这一切都被隐藏起来了,由JS引擎帮助我们完 成。我们需要做的就是加上关键字,在函数中使用await来执行异步操作。这样,可以大大的简 化异步操作。同时,能够像同步方法一样去处理它们。

接下来我们再来看看更细节的一些问题。await后面必须是一个Promise对象,这个很好理解。因 为该Promise对象会返回给外部的执行器,并在异步动作完成后执行resolve,这样外部就可以通 过回调函数处理它,并将结果传递给生成器。

 

 

那如果await后面跟的不是Promise对象又会发生什么呢?

 

const delay = async () => {

let data = await "hello";

console.log(data);

}

这样的代码是允许的,不过await会自动将hello字符串包装一个Promise对象。就像这样:

let data = await new Promise((resolve,reject) => resolve("hello"));

创建了 Promise对象后,立即执行resolve,并将字符串hello传递给外部的执行器。外部执行器的 回调函数再将这个hello传递回来,并赋值给data变量。所以,执行该代码后,马上就会输出字符 hello。虽然代码能够这样写,但是await在这里的意义并不大,所以await还是应该用来处理异 步方法,同时该异步方法应该使用Promise对象。

async函数里面除了有await关键字外,感觉和其他函数没什么区别,那它能有返回值吗?答案是 肯定的,

const delay = async () => {

await new Promise((resolve) => {setTimeout(()=>{resolve()},2000)}); return "finish";

}

let result = delay();

console.log(result);

 

在delay函数中先执行等待2秒的异步操作,然后返回字符串finish。外部调用时我用一个变量接收 它的返回值。最后输出的结果是:

//没有任何等待立即输出

Promise { <pending> }

// 2秒后程序结束

我们可以看到,没有任何等待立即输出了一个Promise对象。而整个程序是在2秒钟后才结束的。 由此看出,获取async函数的返回结果实际上是return出来的一个Promise对象。假如return后面 跟着的本来就是一个Promise对象,那么它会直接返回。但如果不是,则会像await—样包裹一个 Promise对象返回。所以,想要得到返回的具体内容应该这样:

const delay = async () => {

await new Promise((resolve) => {setTimeout(()=>{resolve()},2000)}); return "finish";

}

let result = delay();

console.log(result);

result.then(function(data){

console.log("data:",data);

});

执行的结果:

//没有任何等待立即输出

Promise { <pending> }

//等待2秒后输出

data: finish

那如果函数没有任何返回值,得到的又是什么呢?我将上面代码中取掉return,再次运行:

//没有任何等待立即输出

Promise { <pending> }

//等待2秒后输出

data: undefined

可以看到,仍然可以得到Promise对象,但由于函数没有返回值,所以就不会有任何数据传递出 来,那么打印的结果就是undefined o

最后我们还是来梳理一下async的执行顺序。大致的顺序为:先执行同步代码,然后通过执行器 来执行async里面的每一句代码,如果有返回值,在外部要通过then()方法的回调函数来接

 

收,最后才被执行,示例如下:

const delay = async () => {

console.log('first');

let data = await new Promise((resolve,reject) => resolve("hello"));

console.log('aa');

console.log(data);

let data2 = await new Promise((resolve) => {setTimeout(()=>{resolve('Yes ')},2000)});

console.log(data2);

return 'World';

}

let result = delay();

console.log(result);

result.then(function(data){

console.log("data:",data);

});

console.log(11);

console.log(22);

// first

// Promise { <pending> }

// 11

// 22

// aa

// hello

// Yes

// data: World

效果:首先执行async函数,打印出first,然后是暂停里面的代码,返回一个promise,来到外 部。在外部执行完所有同步的代码,输出Promise { vpending> } , 11和22。接下来回到

async函数,输出aa和hello,然后等两秒钟后,输出Yes,最后回到外部,执行then() 法,打印出data: World。

async的基本原理我们清楚了,下面我们把之前的AJAX例子用async重写下:

Mock.mock(/\.json/,{

'stuents|5-10' : [{

'id|+1' : 1,

'name' : '@cname',

'gende r' : /[男女]/, //在正则表达式匹配的范围内随机 'age|15-30' : 1, //年龄在 15-30之间生成, 1只是用来确定数据类型 'phone' : /1\d{10}/,

'addr' : '@county(true)', //随机生成中国的一个省、市、县数据

'date' : "@date('yyyy-MM-dd')"

}]

 

});

async function getUsers(){ let data = await new Promise((resolve,reject) => {

$.ajax({ type:"get", url:"/users.json", success:function(data){ resolve(data)

}

});

}); console.log("data",data);

}

getUsers();

这是用JQuery的AJAX方法实现。

async function getUsers(){

let response = await fetch("/users");

let data = await response.json(); console.log("data",data);

} getUsers();

这是f etch方法的实现。

从这两个例子可以看出,async和生成器两种方式都很类似,但async可以不借助任何的第三方模 块,也更易于理解,async表示该函数要做异步处理。await表示后面的代码是一个异步操作,等 待该异步操作完成后再执行后面的动作。如果异步操作有返回的数据,则在左边用一个变量来接 收它。

我们知道,await可以让异步操作变为同步的效果。但是,有的时候为了提高效率,我们需要让 多个异步操作同时进行怎么办呢?方法就是执行异步方法时不加await,这样它们就可以同时进 彳丁,然后在获取结果时用await。比如:

function time(ms){

return new Promise((resolve,reject) => { setTimeout(()=>{resolve()},ms);

});

}

const delay = async () => {

let t1 = time(2000);

let t2 = time(2000);

await t1;

console.log("t1 finish"); await t2;

console.log("t2 finish");

}

delay();

我先把时间函数的异步操作封装成了函数,并返回Promise对象。在dela y函数中调用了两次time 方法,但没有用await。也就是说这两个时间函数的执行是"同时"(其实还是有先后顺序)进行 的。然后将它们的Promise对象分别用t1和t2表示。先用await t1。表示等待t1的异步处理完成, 然后输出t1 finish。接着再用await t2,等待t2的异步处理完成,最后输出t2 finish。由于这两个 时间函数是同时执行,而且它们的等待时间也是一样的。所以,当2秒过后,它们都会执行相应 的回调函数。运行的结果就是:等待2秒后,先输出t1 finish,紧接着立即输出t2 finish。

const delay = async () => {

await time(2000);

console.log("t1 finish");

await time(2000);;

console.log("t2 finish");

}

如果是这样写,那么执行的结果会是等待2秒后输出t1 finish。再等待2秒后输出t2 finish。async 确实是一个既好用、又简单的异步处理方法。但是它的问题就是不兼容老的浏览器,只有支持了 ES7的浏览器才能使用它。

 

最后,还需要注意一个问题:await关键字必须写在async定义的函数中。

posted @ 2019-08-14 11:12  哲无止境  阅读(110)  评论(0编辑  收藏  举报