​    绝大多数nodejs程序员都会使用 async 和 await 关键字,但是极少有人能真正弄明白 async 和 await 的原理。这篇文章将从零“构建”出 async 和 await 关键字,从而帮助理清 async 和 await 的本质。

先用一句话概括:async 和 await 是内置了执行器的 generator 函数。

什么是 generator 函数?顾名思义,generator 函数就是一个生成器。生成的是一个可以多次通过 .next() 迭代的对象,例如,定义一个 generator 函数如下:

let g = function* () {
yield 1
yield 2
return 3
}

其中,yield 关键字定义每次迭代的返回值,最后一个返回值用 return。

然后,就可以用它来生成一个可迭代的对象:

let iter = g()

console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

以上代码执行的结果是:

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: true }
{ value: undefined, done: true }

generator 函数也可以接收参数:

let g = function* (a, b) {
yield a
yield b
return a + b
}

let iter = g(1, 2)

console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

执行结果:

{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: true }
{ value: undefined, done: true }

接下来是一个关键点:yield 关键字的值和 .next() 的参数的关系:

let g = function* () {
let ret = yield 1
return ret
}

let iter = g()

console.log(iter.next())
console.log(iter.next(2))

以上代码的执行结果是:

{ value: 1, done: false }
{ value: 2, done: true }

可以看出,第二次调用 .next() 的时候,传入了参数2,这个 2 被赋值给了 ret。也就是说,

  let ret = yield 1

这行代码其实是被拆成两段执行的。第一次调用 .next() 的时候,g 里面的代码开始执行,执行到了 yield 1 这里,就暂停并返回了。这时打印 .next() 的返回值是 { value: 1, done: false }。然后,执行 .next(2) 的时候,又回到了 g 里面的代码,从 let ret = 2 开始执行。

理清楚这一执行过程非常重要。因为,这意味着:

如果我在 g 里面 yield 一个 Promise 出去,在外面等 Promise 执行完之后,再通过 .next() 的参数把结果传进来,会怎样呢?

let asyncSum = function(a, b) {
return new Promise(resolve => {
setTimeout(() => {
resolve(a + b)
}, 1000)
})
}

let g = function* () {
let ret = yield asyncSum(1, 2)
console.log(ret)
return ret
}

let iter = g()

let p = iter.next().value
p.then(sum => {
iter.next(sum)
})

执行结果就是等待一秒之后打印出3:

// 这里挂起了一秒钟
3

请细细品味上面代码里面的 g 函数:

let g = function* () {
let ret = yield asyncSum(1, 2)
console.log(ret)
return ret
}

将其与下面代码进行对比:

let g = async function () {
let ret = await asyncSum(1, 2)
console.log(ret)
return ret
}

发现了吧?事实上 async 函数就是 generator 函数。

读者会问了,不对啊,我们调用 async 函数,都是直接调用,返回一个 Promise ,而不用像上面调用 g 那么麻烦的。

没错。上面调用 g 的代码:

let iter = g()

let p = iter.next().value
p.then(sum => {
iter.next(sum)
})

叫做 g 的执行器。我们可以把它封装起来:

let executor = function() {
return new Promise(resolve => {
let iter = g()

let p = iter.next().value
p.then(sum => {
let ret = iter.next(sum)
resolve(ret.value)
})
})
}

executor().then(ret => {
console.log(ret)
})

执行结果:

// 挂起一秒钟
3 // g 里面的 console.log(ret)
3 // .then 里面的 console.log(ret)

实际上,node的执行引擎悄悄地帮我们做了上面的事情,当我们直接调用一个 async 函数时,其实是在调用它的执行器。

原理讲到这里就完了。下面是扩展部分。

上面的 executor 函数是仅仅针对这个例子里面的 g 写的。那我们是否可能写一个通用的执行器函数,适用于任何 generator 函数呢?不管 generator 函数里面有多少个 yield ,这个执行器是否都可以自动全部处理完?

答案当然是肯定的,用到了递归,请看完整代码:

let asyncSum = function(a, b) {
return new Promise(resolve => {
setTimeout(() => {
resolve(a + b)
}, 1000)
})
}

let asyncMul = function(a, b) {
return new Promise(resolve => {
setTimeout(() => {
resolve(a * b)
}, 1000)
})
}

let g = function* (a, b) {
let sum = yield asyncSum(1, 2)
let ret = yield asyncMul(sum, 2)
return ret
}

function executor(generator, ...args) {
let iter = generator.apply(this, args)
let n = iter.next()
if (n.done) {
return new Promise(resolve => resolve(n.value))
} else {
return new Promise(resolve => {
n.value.then(ret => {
_r(iter, ret, resolve)
});
});
}
}

function _r(iter, ret, resolve) {
let n = iter.next(ret)
if (n.done) {
resolve(n.value)
} else {
n.value.then(ret => {
_r(iter, ret, resolve)
})
}
}

executor(g, 1, 2).then(ret => {
console.log(ret)
})

执行结果:

// 这里挂起了两秒钟
6

不过上面这个 executor 是个不完善的版本,因为没有考虑错误的情况。其实早在 async 和 await 还没有出现的 2013 年,著名程序员 TJ Holowaychuk 就写了一个完善的 generator 执行器。项目地址:https://github.com/tj/co 。其名字叫 co。典型用法就是:

co(function* () {
var result = yield Promise.resolve(true);
return result;
}).then(function (value) {
console.log(value);
}, function (err) {
console.error(err.stack);
});

关于 async 和 await 的本质,到这里就结束啦。文章最后请细心的读者思考一个问题:为什么 TJ Holowaychuk 的这个模块名字要叫做 co?

[NodeJS] async 和 await 的本质的更多相关文章

  1. 关于async和await的一些误区实例详解

    转载自 http://www.jb51.net/article/53399.htm 这篇文章主要介绍了关于async和await的一些误区实例详解,有助于更加深入的理解C#程序设计,需要的朋友可以参考 ...

  2. 关于async和await的一些误区

    微软的MSDN说async和await是“异步”,但是不少人(包括笔者自己)有一些误区需要澄清:为什么await语句之后没有执行?不是异步吗? [示例代码] public partial class ...

  3. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  4. Async和Await异步编程的原理

    1. 简介 从4.0版本开始.NET引入并行编程库,用户能够通过这个库快捷的开发并行计算和并行任务处理的程序.在4.5版本中.NET又引入了Async和Await两个新的关键字,在语言层面对并行编程给 ...

  5. Async 和 Await的性能(.NET4.5新异步编程模型)

    异步编程长时间以来一直都是那些技能高超.喜欢挑战自我的开发人员涉足的领域 — 这些人愿意花费时间,充满热情并拥有心理承受能力,能够在非线性的控制流程中不断地琢磨回调,之后再回调. 随着 Microso ...

  6. 编程概念--使用async和await的异步编程

    Asynchronous Programming with Async and Await You can avoid performance bottlenecks and enhance the ...

  7. Async 与 Await 关键字研究

    1        Aynsc 和 Await 关键字的研究 在 .NET 4.0 以后,基于 Task 的异步编程模式大行其道,因其大大简化了异步编程所带来的大量代码工作而深受编程人员的欢迎,如果你曾 ...

  8. async/task/await

    async/task/await三组合是.NET Framework 4.5带给.NET开发者的大礼,合理地使用它,可以提高应用程序的吞吐能力. 但是它的使用有点绕人,如果不正确使用,会带来意想不到的 ...

  9. 不使用回调函数的ajax请求实现(async和await简化回调函数嵌套)

    在常规的服务器端程序设计中, 比如说爬虫程序, 发送http请求的过程会使整个执行过程阻塞,直到http请求响应完成代码才会继续执行, 以php为例子 $url = "http://www. ...

随机推荐

  1. perf4j @Profiled常用写法

    以下内容大部分摘抄自网络上信息. 1.默认写法 @Profiled 日志语句形如: 2009-09-07 14:37:23,734 [main] INFO org.perf4j.TimingLogge ...

  2. mysql 优化配置和方面

    MySQL性能优化的参数简介 公司网站访问量越来越大,MySQL自然成为瓶颈,因此最近我一直在研究 MySQL 的优化,第一步自然想到的是 MySQL 系统参数的优化,作为一个访问量很大的网站(日20 ...

  3. 苹果联合创始人高调宣布弃用Facebook是什么梗?

    这段时间,扎克伯格非常郁闷.泄密丑闻不仅让Facebook股价大跌.引来审查等,还被众多互联网.科技大佬批判.孤立.如,"钢铁侠"马斯克就直接删除了SpaceX 和特斯拉的 Fac ...

  4. SpringSecurity 如何提示错误

    1.可以通过authentication-failure-url="/login.html?error=1" 前端接收参数,根据参数提示 错误 2.前端vue this.myNam ...

  5. python Django请求生命周期

    首先我们知道HTTP请求及服务端响应中传输的所有数据都是字符串. 在Django中,当我们访问一个的url时,会通过路由匹配进入相应的html网页中. Django的请求生命周期是指当用户在浏览器上输 ...

  6. 跟随大神实现简单的Vue框架

    自己用vue也不久了,学习之初就看过vue实现的原理,当时看也是迷迷糊糊,能说出来最基本的,但是感觉还是理解的不深入,最近找到了之前收藏的文章,跟着大神一步步敲了一下简易的实现,算是又加深了理解. 原 ...

  7. 什么是AWVS

    什么是AWVS Acunetix Web Vulnerability Scanner(简称AWVS)是一款知名的网络漏洞扫描工具,它通过网络爬虫测试你的网站安全,检测流行安全漏洞,现已更新到10.(下 ...

  8. OpenGL Panorama Player

    JMGL_PANO star_war_eve source 1 star_war_eve source 2 1. 介绍 JMGL_PANO 是Justin开源的一个全景视频播放器(Github).基于 ...

  9. first-child和first-of-type

    我想实现的效果:将第一个article字体颜色设置为红色 123456 <div? <h1>logo</h1> <article>article1</a ...

  10. MIZ702N开发环境的准备1

    前言 最近由于工作需要开始接手基于MIZ702的硬件平台的Linux的开发,仔细想想,工作这么久,这好像还是我第一次接手嵌入式Liunx相关的工作.这几天拿到开发板,开始了阅读文档.安装Ubuntu虚 ...