回顾一下异步编程的历史
在ES6之前,处理异步一般使用回调函数,但回调函数缺陷比较大,耦合性强,等诸多缺点。甚至还产生了一个叫做回调地狱的专有名词。
$.Ajax({ success:(e)=>{ $.ajax({ success:(e)=>{ $.ajax({ success:(e)=>{ } }) } }) } })
在ES6之后,开始引入Promise来解决回调函数的问题,Promise的介绍合集文章里已有,感兴趣的朋友可以阅读一下。虽然Promise更合理也更强大,但总感觉还是差那么点意思。
$.get("a.url").then(()=>{ return $.get("b.url") }).then(()=>{ return $.get("c.url") }).then(()=>{ return $.get("d.url") })
然后Generator诞生了,然后还是差那么一点意思,最后面就有了Async Await。可以说Async就是异步编程的终极解决方案、了,它可以让我们感觉像在编写同步代码一样编写异步代码。从人的角度也对代码更易理解。所以结合它们的关系我们可以概括为回调函数促使了Promise,Generator的诞生,然后衍生出了Async。
Generator
首先Generator它是ES6推出的一种数据类型,它看起来似乎跟函数非常相近。它使用function*来创建一个Generator对象,叫做生成器函数 (generator function)语法如下
function* name([param[, param[, ... param]]]) { statements }
我们看一段Generator的代码实例
function* test() { yield 1; yield 2; return 3; } console.log(test().next()) //错误调用 console.log(test().next()) //错误调用 console.log(test().next()) //错误调用 console.log("错误示范结束") Let a=test() //调用方法1 next() console.log(a.next()) console.log(a.next()) console.log(a.next()) console.log("调用方法1结束") //调用方法2 使用for循环 for (let i of test()){ console.log(i) } console.log("调用方法2结束")
运行结果如下
通过上面的实例,我们可以总结如下Generator的特点。
它必须先用一个变量来接收。直接调用如示例中的test()等于又重新运行了一次。
它可以多次返回数据,而不像函数一样只返回一次数据。
它有2种调用方式。一种是.next()一种是用for循环,但都是从上往下的执行。
next()返回{value: number, done: bool}对象,value是返回值,done为true时代表所有动作已完成。
其他关于Generator请往下看
当function* 并没有给出return的时候,在执行完yield后讲返回{value: undefined, done: true}
yield本身是没有返回值的,所以我们在.next(value)的时候可以加入value,以替代上一个yield的返回值
也可以使用Generator.prototype.return(value)方法提前结束。如示例中a.return(5)在还存在yield的时候将提前返回{value: 5, done: true}
另外还有Generator.prototype.throw()可以向Generator抛出错误并恢复生成器的执行示例代码如下
function* tmp() { try { yield 1; } catch(e) { console.log("err"); } } let g=tmp() g.next(); // { value: 1, done: false } g.throw(new Error("Something went wrong")); // "err"
Async/Await
我们可以理解Async为Generator的语法糖,但是它返回值是Promise,Async比Generator在语法还更简洁和明了。它通过async function来表明这是一个异步的函数。在我们看一段示例
function g(x){ return new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(x) console.log(x); },x) }) } async function tmp(){ await g(300); await g(200); await g(100); return "上面3个都成功执行了" } tmp().then(res=>{ console.log(res) })
上述代码运行后,控制台打印效果如下
虽然await都是执行的延时代码但它的运行顺序是从上往下的跟同步的效果是一样的,并不根据时间的先后顺序来执行。然后异步函数tmp通过.then来读取return的值。当然await并不一定需要在async里,它可以单独出现。如以下示例
function g(x){ return new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(x) console.log(x); },x) }) } await g(300); await g(200); await g(100); //在语法上await强制规定其应该使用在async里,但我这样运行了并没报错。当然,为了规范性最好还是用在async。
我们整改一下代码把g(x)改动如下在运行代码
function g(x){ setTimeout(()=>{ console.log(x); },x) } async function tmp(){ await g(300); await g(200); await g(100); return "上面3个都成功执行了" } tmp().then(res=>{ console.log(res) })
这里就出现了小状况。g(x)除去掉Promise后,这就是完全的异步效果了啊。按照时间的先后顺序来执行,这是什么原因呢?这里就要说一下await的工作原理了。
当async执行开始,它碰await。它会先问一下await你是什么类型?await回答“我是字符串”。那么async就继续往下执行,但是如果await回答“我是Promise”,那么async就会等待await干完活后再继续往下执行。
所以只有当await为Promise时会阻塞async,否则await直接返回赋予的值。这也是为什么上诉示例代码去掉Promise后程序就是完全的按照异步的方式来执行的。所以我们总结如下
await表达式一般为Promise,也允许可以是其他任意值。当然如果不用Promise那为什么要用异步呢?
当await为Promise时会阻塞async函数,否则直接做为await的返回值。
最后总结
上面我们做了Promise Generator async/await的介绍,也给出了相应的例子。我个人感觉使用Promise更像是再编程,手撸代码,是非常正规的格式,如果是使用Generator它的语法我就感觉奇奇怪怪的。所以综合下来最优和最简洁的肯定就是async/await,也很符合我们人类的阅读习惯。当然,上诉三种方法肯定都有自己各种最擅长的领域,再实际中就看大家如何选择了。
另外,其实它们归根结底基本还是为了Ajax来服务的。虽然我上边没用Ajax只是为了让示例代码尽量的少。我相信大家肯定会有举一反三的能力,就不多累赘代码了。本人QQ/微信24722,如有前端需要的可以联系我啊!