说一说javascript的异步编程

众所周知javascript是单线程的,它的设计之初是为浏览器设计的GUI编程语言,GUI编程的特性之一是保证UI线程一定不能阻塞,否则体验不佳,甚至界面卡死。 所谓的**单线程**就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着。 ![图片来自网络](upload/201811081024346083.jpg) 这种模式的好处是实现起来简单,执行环境相对单纯,坏处就是只要有一个任务耗时很长,后面的任务都会必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致了整个页面卡在这个地方,其他任务无法执行。 为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。 “同步”就是上面所说的,后面的任务等待上一个任务结束,然后再执行。 #### 什么是“异步”? 所谓异步简单说就是一个任务分成两段,先执行一段,转而执行其他任务,等做好了准备转而执行第二段。 以下是当有ABC三个任务,同步或异步执行的流程图: 同步 ``` thread ->|----A-----||-----B-----------||-------C------| ``` 异步: ``` A-Start ---------------------------------------- A-End | B-Start ----------------------------------------|--- B-End | | C-Start -------------------- C-End | | V V V V V V thread-> |-A-|---B---|-C-|-A-|-C-|--A--|-B-|--C--|---A-----|--B--| ``` "异步"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。 本文简单梳理总结了JavaScript异步函数的发展历史如下图: ![图片来自网络](upload/201811081024445048.png) 当你没有现成的Promise时,你可能需要借助一些Promise库,一个流行的选择是使用 [bluebird](https://github.com/petkaantonov/bluebird)。 这些库可能会提供比原生方案更多的功能,并且不局限于Promise/A+标准所规定的特性。 ## Generator(ECMAScript6)+co JavaScript [生成器](https://blog.risingstack.com/introduction-to-koa-generators/)是个相对较新的概念, 它是ES6(也被称为ES2015)的新特性。想象下面这样的一个场景: >当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。 上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。 ``` function* foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操作 } } var bar = foo(); // 返回的其实是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true } ``` 更进一步的,如果你想更轻松的使用生成器函数来编写异步JavaScript代码,我们可以使用 [co](https://www.npmjs.com/package/co) 这个库,co是著名的tj大神写的。 >Co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。 使用co,前面的示例代码,我们可以使用下面的代码来改写: ``` co(function* (){ yield Something.save(); }).then(function() { // success }) .catch(function(err) { //error handling }); ``` 你可能会问:如何实现并行操作呢?答案可能比你想象的简单,如下(其实它就是Promise.all而已): ``` yield [Something.save(), Otherthing.save()]; ``` ## 终极解决方案Async/ await 简而言之,使用async关键字,你可以轻松地达成之前使用生成器和co函数所做到的工作。 在这背后,async函数实际使用的是Promise,这就是为什么async函数会返回一个Promise的原因。 因此,我们使用async函数来完成类似于前面代码所完成的工作,可以使用下面这样的方式来重新编写代码: ``` async function save(Something) { try { await Something.save(); // 等待await后面的代码执行完,类似于yield } catch (ex) { //error handling } console.log('success'); } ``` 使用async函数,你需要在函数声明的最前面加上async关键字。这之后,你可以在函数内部使用await关键字了,作用和之前的yield作用是类似的。 使用async函数完成并行任务与yiled的方式非常的相似,唯一不同的是,此时Promise.all不再是隐式的,你需要显示的调用它: ``` async function save(Something) { await Promise.all[Something.save(), Otherthing.save()] } ``` Async/Await是异步操作的终极解决方案,Koa 2在node 7.6发布之后,立马发布了正式版本,并且推荐使用async函数来编写Koa中间件。 这里给出一段Koa 2应用里的一段代码: ``` exports.list = async (ctx, next) => { try { let students = await Student.getAllAsync(); await ctx.render('students/index', { students : students }) } catch (err) { return ctx.api_error(err); } }; ``` 它做了3件事儿 - 通过await Student.getAllAsync();来获取所有的students信息。 - 通过await ctx.render渲染页面 - 由于是同步代码,使用try/catch做的异常处理 之后还会分享node的基本概念和eventLoop(宏任务和微任务) (完) 参考: [The Evolution of Asynchronous JavaScript](https://blog.risingstack.com/asynchronous-javascript/) 标签: javascript, 异步编程 好文要顶 关注我https://www.cnblogs.com/chenshufang/p/9927536.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信