首页你好哇,世界马蹄疾
Async专题:也许是终极异步解决方案
马蹄疾
2019-05-06 18:26:50

上一章我们了解了co与Generator结合的异步编程解决方案。

我知道你想说什么,写一个异步调用还得引入一个npm包(虽然是大神TJ写的包)。

妈卖批的npm!

当然是不存在的。如果一个特性足够重要,社区的呼声足够高,它就一定会被纳入标准的。马上我们要介绍的就是血统纯正的异步编程家族终极继承人——爱新觉罗·async。

import co from 'co'; function fetchByName(name) { const url = `https://api.github.com/users/${name}/repos`; return fetch(url).then(res => res.json()); } co(function *gen() { const value1 = yield fetchByName('veedrin'); console.log(value1); const value2 = yield fetchByName('tj'); console.log(value2); });
function fetchByName(name) { const url = `https://api.github.com/users/${name}/repos`; return fetch(url).then(res => res.json()); } async function fetchData() { const value1 = await fetchByName('veedrin'); console.log(value1); const value2 = await fetchByName('tj'); console.log(value2); } fetchData();

看看这无缝升级的体验,啧啧。

灵活

别被新的关键字吓到了,它其实非常灵活。

async function noop() { console.log('Easy, nothing happened.'); }

这家伙能执行吗?当然能,老伙计还是你的老伙计。

async function noop() { const msg = await 'Easy, nothing happened.'; console.log(msg); }

同样别慌,还是预期的表现。

只有当await关键字后面是一个Promise的时候,它才会显现它异步控制的威力,其余时候人畜无害。

function fetchByName(name) { const url = `https://api.github.com/users/${name}/repos`; return fetch(url).then(res => res.json()); } async function fetchData() { const name = await 'veedrin'; const repos = await fetchByName(name); console.log(repos); }

虽然说await关键字后面跟Promise或者非Promise都可以处理,但对它们的处理方式是不一样的。非Promise表达式直接返回它的值就是了,而Promise表达式则会等待它的状态从pending变为fulfilled,然后返回resolve的参数。它隐式的做了一下处理。

注意看,fetchByName('veedrin')按道理返回的是一个Promise实例,但是我们得到的repos值却是一个数组,这里就是await关键字隐式处理的地方。

另外需要注意什么呢?await关键字只能定义在async函数里面。

const then = Date.now(); function sleep(duration) { return new Promise((resolve, reject) => { const id = setTimeout(() => { resolve(Date.now() - then); clearTimeout(id); }, duration * 1000); }); } async function work() { [1, 2, 3].forEach(v => { const rest = await sleep(3); console.log(rest); return '睡醒了'; }); } work(); // Uncaught SyntaxError: await is only valid in async function

行吧,那我们把它弄到一个作用域里去。

import sleep from './sleep'; function work() { [1, 2, 3].forEach(async v => { const rest = await sleep(3); console.log(rest); }); return '睡醒了'; } work();

不好意思,return '睡醒了'没等异步操作完就执行了,这应该也不是你要的效果吧。

所以这种情况,只能用for循环来代替,async和await就能长相厮守了。

import sleep from './sleep'; async function work() { const things = [1, 2, 3]; for (let thing of things) { const rest = await sleep(3); console.log(rest); } return '睡醒了'; } work();

返回Promise实例

有人说async是Generator的语法糖。

naive,朋友们。

async可不止一颗糖哦。它是Generator、co、Promise三者的封装。如果说Generator只是一个状态机的话,那async天生就是为异步而生的。

import sleep from './sleep'; async function work() { const needRest = await sleep(6); const anotherRest = await sleep(3); console.log(needRest); console.log(anotherRest); return '睡醒了'; } work().then(res => console.log('🙂', res), res => console.error('😡', res));

因为async函数返回一个Promise实例,那它本身return的值跑哪去了呢?它成了返回的Promise实例resolve时传递的参数。也就是说return '睡醒了'在内部会转成resolve('睡醒了')

我可以保证,返回的是一个真正的Promise实例,所以其他特性向Promise看齐就好了。

并发

也许你发现了,上一节的例子大概要等9秒多才能最终结束执行。可是两个sleep之间并没有依赖关系,你跟我说说我凭什么要等9秒多?

之前跟老子说要异步流程控制是不是!现在又跟老子说要并发是不是!

我…满足你。

import sleep from './sleep'; async function work() { const needRest = await Promise.all([sleep(6), sleep(3)]); console.log(needRest); return '睡醒了'; } work().then(res => console.log('🙂', res), res => console.error('😡', res));
import sleep from './sleep'; async function work() { const onePromise = sleep(6); const anotherPromise = sleep(3); const needRest = await onePromise; const anotherRest = await anotherPromise; console.log(needRest); console.log(anotherRest); return '睡醒了'; } work().then(res => console.log('🙂', res), res => console.error('😡', res));

办法也是有的,还不止一种。手段都差不多,就是把await往后挪,这样既能搂的住,又能实现并发。

大总结

关于异步的知识大体上可以分成两大块:异步机制与异步编程。

异步机制的精髓就是事件循环。

通过控制权反转(从事件通知主线程,到主线程去轮询事件),完美的解决了一个线程忙不过来的问题。

异步编程经历了从回调Promiseasync的伟大探索。异步编程的本质就是用尽可能接近同步的语法去处理异步机制。

async目前来看是一种比较完美的同步化异步编程的解决方案。

但其实async是深度集成Promise的,可以说Promiseasync的底层依赖。不仅如此,很多API,诸如fetch也是将Promise作为底层依赖的。

所以说一千道一万,异步编程的底色是Promise

Promise是通过什么方式来异步编程的呢?通过then函数,then函数又是通过回调来解决的。

所以呀,回调才是刻在异步编程基因里的东西。你大爷还是你大爷!

回调换一种说法也叫事件。

这下你理解了为什么说JavaScript是事件驱动的吧?

#JavaScript
@Async
0comments withand markdown
输入"@+用户名+空格"可以AT某人,AT某人时可以按TAB键补全。
输入"登记过的邮箱"会自动补全剩余信息,可以登记原账号,也可以修改用户名再登记原账号。
先登记后评论,乖
性别: