ECMAScript 6(简称ES6)将 JavaScript 异步编程带入了一个全新的阶段。这篇文章的主题北海就是介绍更强大、更完善的 ES6 异步编程方法。
首先我们回顾一下javascript异步的发展历程。
ES6 以前:
回调函数(callback):nodejs express 中常用北海ajax中常用。
ES6:
promise对象:nodejs最早有bluebird promise的雏形北海axios中常用。
generator函数:nodejs koa框架使用率很高。
ES7:
async/await语法:当前最常用的异步语法北海nodejs koa2 完全使用该语法。
目录
什么是异步
回调函数callback
promise对象
协程
Generator 函数
Generator 函数的用法
async-await
什么是异步编辑本段回目录
所谓"异步"北海简单说就是一个任务分成两段北海先执行第一段北海然后转而执行其他任务北海等做好了准备北海再回过头执行第二段。比如北海有一个任务是读取文件进行处理北海异步的执行过程就是下面这样。
异步
上图中北海任务的第一段是向操作系统发出请求北海要求读取文件。然后北海程序执行其他任务北海等到操作系统返回文件北海再接着执行任务的第二段(处理文件)。
这种不连续的执行北海就叫做异步。相应地北海连续的执行北海就叫做同步。
同步
上图就是同步的执行方式。由于是连续执行北海不能插入其他任务北海所以操作系统从硬盘读取文件的这段时间北海程序只能干等着。
回调函数callback编辑本段回目录
JavaScript 语言对异步编程的实现北海就是回调函数。所谓回调函数北海就是把任务的第二段单独写在一个函数里面北海等到重新执行这个任务的时候北海就直接调用这个函数。它的英语名字 callback北海直译过来就是"重新调用"。
回调字面也好理解北海就是先处理本体函数北海再处理回调的函数北海举个例子北海方便大家理解。
上面的例子很好理解北海首先执行主体函数A北海打印结果:我是主题函数;
然后执行回调函数callback 也就是B北海打印结果:我是回调函数。
promise对象编辑本段回目录
promise 对象用于一个异步操作的最终完成(或最终失败)及其结果的表示。
简单地说就是处理一个异步请求。我们经常会做些断言北海如果我赢了你就嫁给我北海如果输了我就嫁给你之类的断言。
这就是promise的中文含义:断言北海一个成功北海一个失败。
举个例子北海方便大家理解:
promise构造函数的参数是一个函数北海我们把它称为处理器函数。
处理器函数接收两个函数reslove和reject作为其参数北海当异步操作顺利执行则执行reslove函数, 当异步操作中发生异常时北海则执行reject函数。
通过resolve传入得的值北海可以在then方法中获取到北海通过reject传入的值可以在chatch方法中获取到。
因为then和catch都返回一个相同的promise对象北海所以可以进行链式调用。
Promise 的写法只是回调函数的改进北海使用then方法以后北海异步任务的两段执行看得更清楚了北海除此以外北海并无新意。
Promise 的最大问题是代码冗余北海原来的任务被Promise 包装了一下北海不管什么操作北海一眼看去都是一堆 then北海原来的语义变得很不清楚。
那么北海有没有更好的写法呢?
协程编辑本段回目录
传统的编程语言北海早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine)北海意思是多个线程互相协作北海完成异步任务。
协程有点像函数北海又有点像线程。它的运行流程大致如下。
第一步北海协程A开始执行。
第二步北海协程A执行到一半北海进入暂停北海执行权转移到协程B。
第三步北海(一段时间后)协程B交还执行权。
第四步北海协程A恢复执行。
上面流程的协程A北海就是异步任务北海因为它分成两段(或多段)执行。
举例来说北海读取文件的协程写法如下。
1.function asnycJob() {
2. // ...其他代码
3. var f = yield readFile(fileA);
4. // ...其他代码
5.}
上面代码的函数 asyncJob 是一个协程北海它的奥妙就在其中的 yield 命令。它表示执行到此处北海执行权将交给其他协程。也就是说北海yield命令是异步两个阶段的分界线。
协程遇到 yield 命令就暂停北海等到执行权返回北海再从暂停的地方继续往后执行。它的最大优点北海就是代码的写法非常像同步操作北海如果去除yield命令北海简直一模一样。
Generator 函数编辑本段回目录
Generator 函数是协程在 ES6 的实现北海最大特点就是可以交出函数的执行权(即暂停执行)。
1.function asnycJob() {
2. // ...其他代码
3. var f = yield readFile(fileA);
4. // ...其他代码 5.}
上面代码就是一个 Generator 函数。它不同于普通函数北海是可以暂停执行的北海所以函数名之前要加星号北海以示区别。
整个 Generator 函数就是一个封装的异步任务北海或者说是异步任务的容器。异步操作需要暂停的地方北海都用 yield 语句注明。Generator 函数的执行方法如下。
1.var g = gen(1);
2.g.next() // { value: 3, done: false }
3.g.next() // { value: undefined, done: true }
上面代码中北海调用 Generator 函数北海会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方北海即执行它不会返回结果北海返回的是指针对象。调用指针 g 的 next 方法北海会移动内部指针(即执行异步任务的第一段)北海指向第一个遇到的 yield 语句北海上例是执行到 x + 2 为止。
换言之北海next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法北海会返回一个对象北海表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值北海表示当前阶段的值;done 属性是一个布尔值北海表示 Generator 函数是否执行完毕北海即是否还有下一个阶段。
Generator 函数的用法编辑本段回目录
下面看看如何使用 Generator 函数北海执行一个真实的异步任务。
1.var fetch = require('node-fetch');
2.function* gen(){
3. var url = 'https://api.github.com/users/github';
4. var result = yield fetch(url);
5. console.log(result.bio);
6.}
上面代码中北海Generator 函数封装了一个异步操作北海该操作先读取一个远程接口北海然后从 JSON 格式的数据解析信息。就像前面说过的北海这段代码非常像同步操作北海除了加上了 yield 命令。
执行这段代码的方法如下。
1.var g = gen();
2.var result = g.next();
3.result.value.then(function(data){
4. return data.json();
5.}).then(function(data){
6. g.next(data);
7.});
上面代码中北海首先执行 Generator 函数北海获取遍历器对象北海然后使用 next 方法(第二行)北海执行异步任务的第一阶段。由于 Fetch 模块返回的是一个 Promise 对象北海因此要用 then 方法调用下一个next 方法。
可以看到北海虽然 Generator 函数将异步操作表示得很简洁北海但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
async-await编辑本段回目录
async函数返回一个promise对象北海如果在async函数中返回一个直接量北海async会通过Promise.resolve封装成Promise对象。
我们可以通过调用promise对象的then方法北海获取这个直接量。
那如过async函数不返回值北海又会是怎么样呢?
await会暂停当前async的执行北海await会阻塞代码的执行北海直到await后的表达式处理完成北海代码才能继续往下执行。
await后的表达式既可以是一个Promise对象北海也可以是任何要等待的值。
如果await等到的是一个 Promise 对象北海await 就忙起来了北海它会阻塞后面的代码北海等着 Promise 对象 resolve北海然后得到 resolve 的值北海作为 await 表达式的运算结果。
上边你看到阻塞一词北海不要惊慌北海async/await只是一种语法糖北海代码执行与多个callback嵌套调用没有区别。
本质并不是同步代码北海它只是让你思考代码逻辑的时候能够以同步的思维去思考北海避开回调地狱。
简而言之-async/await是以同步的思维去写异步的代码北海所以async/await并不会影响node的并发数北海大家可以大胆的应用到项目中去!
如果它等到的不是一个 Promise 对象北海那 await 表达式的运算结果就是它等到的东西。
举个例子北海方便大家理解: