异步操作一直以来都是JavaScript开发者十分热衷的话题,从callback、promise到generator再到async,其过程就像一首奏鸣曲,在向我们讲述着JavaScript异步操作的漫漫进程。
Callback hell的峥嵘岁月
Callback Hell
在ES6之前,我们更多的是采用callback的方式处理异步的过程,http://callbackhell.com/(如无法打开请复制链接至浏览器打开)中将类似于以下代码中诸多的’)}’这种形状称为’回调地狱’:
(注:左右/上下滑动屏幕可查看全部代码)
显然,没有人会保持足够的耐心去阅读这样的代码-----如果嵌套的层级更深的话.
怎么解决Callback Hell
我们先看如下的一段Javascript代码:
(注:左右/上下滑动屏幕可查看全部代码)
先读取example.txt中的关键字段,查询数据库后根据结果请求数据,其中包含的异步操作有:读取文件、查询数据库、请求数据,这种多层的嵌套显然是不利于阅读与维护的,为了解决这种问题,保持代码的结构整齐、浅显易读不失为一种好的方法:
(注:左右/上下滑动屏幕可查看全部代码)
对比之下,通过对函数进行命名或者是将函数封装成独立模块,代码会更具有阅读性。
Promise的应运而生
Promise
在JavaScript中,万物皆对象,Promise也不例外。我们可以将Promise当做一个包含异步操作并且可以被外界获取的对象,而这个对象有着自身的特点:
(1)内部状态不受外界影响
Promise中有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected,只要产生这两种状态,就不会改变了,就被称为resolved。
在ES6中,Promise是一个构造函数,接收一个函数作为参数,这个函数的参数有两个:resolve和reject(也是函数类型)。resolve函数的作用是将Promise的状态从pending转变为resolved,异步操作成功时传递出异步操作的结果。reject函数的作用是将状态从pending转变为rejected,在异步操作失败时传出错误。
Promise的实例
通过Promise实现一个ajax:
(注:左右/上下滑动屏幕可查看全部代码)
虽然Promise有着非常灵活的使用方式,但是仍不免存在一些缺点:
无法中途取消Promise、无法直接从外部知道Promise抛出的错误、无法具体了解pending状态中的阶段。而Promise的这些问题却在我们的下一个话题---generator中得到了解决。
Generator的顺势而为
Generator函数
Generator函数是ES6提供的一种异步编程解决方案。
语法上,可以把它理解成:Generator函数是一个状态机,封装了多个内部状态。形式上,Generator函数是一个普通函数。整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。
Generator函数有两个特征:
(1)function关键字和函数之间有一个星号(*),且内部使用yield表达式,定义不同的内部状态。
(2)调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
执行Generator函数会返回一个遍历器对象,可以依次遍历函数内部的每一个状态。这个遍历器对象有三个方法:next()、throw()、return().
Next()
next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段:
(注:左右/上下滑动屏幕可查看全部代码)
上面代码中,第一个next()方法返回表达式x+2的值;第二个next()带有参数2,2作为上个阶段异步任务的返回结果,被函数内的y接收,再return出来。
Throw()
Throw方法用来补货函数体外抛出的错误:
(注:左右/上下滑动屏幕可查看全部代码)
Return()
Return方法用来终结遍历generator函数:
(注:左右/上下滑动屏幕可查看全部代码)
用于发送请求返回结果:
(注:左右/上下滑动屏幕可查看全部代码)
由此可见通过generator来控制异步的过程会更加简洁,而generator的yeild后面还可以是promise对象,因此在异步处理中相对会更灵活、更简便。
async-await的兼容并包
async函数
在es7中引入的async函数使得处理异步操作更加方便:
直接在普通函数前面加上async,表示这是一个异步函数,在要异步执行的语句前面加上await,表示后面的表达式需要等待。我们通过下面的例子先简单了解一下:
(注:左右/上下滑动屏幕可查看全部代码)
上面的代码指定50毫秒后输出hello world
async函数有多种使用方式:
//函数声明式:
async function foo() {}
//函数表达式:
const foo = async function () {}
//对象的方法:
let obj = }
obj.foo().then()
//箭头函数:
const foo = async () => {}
async函数返回一个promise对象,可以使用then方法添加回调函数:
(注:左右/上下滑动屏幕可查看全部代码)
Await命令后面是一个promise对象,如果不是会被转成一个立即resolve的promise对象:
(注:左右/上下滑动屏幕可查看全部代码)
通过async函数来发送多个请求:
(注:左右/上下滑动屏幕可查看全部代码)
由此可见,async函数不仅包含了promise、generator,代码也更加简洁。
总的来说,async函数就是generator函数的语法糖,相对于generator函数async函数有以下优点:
1.有内置执行器,不用调用next
Generator函数是需要调用next指令来运行异步的语句,async不需要调用next,直接像运行正常的函数那样运行就可以
2.有更好的语义化
语义化更明确,相比较于Generator的*和yield,async和await更明确。
3. await后面可以跟promise或者任意类型的值
yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
4.返回一个promise对象,可以调用.then
async直接返回一个promise对象,可以用then和catch来处理。
— END —
撰文★程金峰
领取专属 10元无门槛券
私享最新 技术干货