JavaScript 异步编程实现

异步实现编程

何为同步、何为异步

每一种语言都有自己的运行机制,JavaScript(下文用JS代替)当然也不例外。说起JS运行机制,大家都会想到它是一门典型的单线程语言。

单线程,顾名思义,单人桥梁,同一时间只允许一个人通过。而JS设计之初是作为浏览器脚本语言,操作DOM,完成用户交互。这就决定它只能是单线程(也可称只有一个主线程的单线程),否则会带来复杂的同步问题。比如,假定JS同时有两个线程,,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?因此需要为他们排列,前一个任务完成,才能执行后一个任务,减少DOM交互冲突。但如果前一个交互事件耗时很长很长,后一个交互事件只能抱着那无处安放才华不甘的等下去。终有一刻,那些不能尽快施展才能的文化人将聚首抗义,引起管道阻塞、浏览器卡死、页面就block不能及时响应用户交互等问题。

莫激动!!虽然JS是单线程,可浏览器内部不是单线程,比如,I/O操作、定时器计时、事件监听(click、mousedown...)等都是由浏览器提供的其他线程来完成的。为了利用多核CPU的计算能力或者说利用多线程来完成,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。

单线程下那些需要排列的任务分为同步任务和异步任务,同步就是按顺序逐一执行。异步任务,不进入主线程、而进入任务队列(task queue或事件队列),只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。实现方式先放入任务队列(或事件队列),当满足某个条件时,就可以从该队列中拿出你当前需要的东西。

异步的进化

说异步的具体实现之前,我们试想一下,怎样的异步更符合我们的编码要求:语义化、易阅读、错误处理(方便调试)。emmmmm...差不多就是这样。那我们用多种方式 由差到优依次来实现异步编程。

1、 setTimeout

实现方式:setTimeout(func|code, delay); 多用于定时器,即经过多少秒后延缓执行,但并非那么精确,详细可搜索“setTimeout事件循环模型”

适用场景: 主要用于定时器。

2、事件监听

实现方式:element.addEventListener(event, function, useCapture); 用于浏览器初次渲染DOM的时候,我们会给一些DOM绑定事件函数,只有当触发了这些DOM事件函数,才会执行他们,典型的发布和订阅事件。

适用场景: 主要用于给以生成的DOM元素绑定事件。

3、回调模式

但我们常常也有这样的要求,一个方法中需要多次回调,并以上一个回调的值作为本次回调的参数,实现如下:

很显然,对比我们之前的异步要求,我们还需要添加错误处理:

显而易见,嵌套随着需求越来越深,最终会形成回调地狱,可读性差,造成代码臃肿,更棘手的是,回调函数之外无法捕获到回调函数中的异常。

适用场景: 较少的回调层(如:简单的Ajax请求)。

3、Promise

因为有些童鞋不太了解Promise,给出demo之前,我们先来讲一下Promise是个什么东东。Promise对象是全局对象,你也可以理解为一个类,创建Promise实例的时候,要有那个new关键字。参数是一个匿名函数,其中有两个参数:resolve和reject,两个函数均为方法。resolve方法用于处理异步操作成功后业务;reject方法用于操作异步操作失败后的业务。此外,Promise还有pending状态,此状态出现在初始化实例后。那方法声明完了,什么时候调用方法,怎么调用方法,执行方法中有了错误,怎么办??别着急,Promise提供了三种方法,为大家排忧解难。话不多说,我们直接"上码"

按照上边的介绍,我们先创建一个Promise实例,并把创建的实例当作getJSON函数的返回值

OK,实例创建完成,开始愉快的造起来。

Listener:等等,这么清爽? 那一大堆的嵌套回调,直接链式then()就解决了?

Speaker:

Listener:很强势,可是Promise的错误处理怎么说呢?

Speaker:别着急,这就是我要讲的第二个方法。

再也不会出现callBack那样对异常的处理:需要在每一个回调里面单独处理。瞬间觉得头顶一阵凉爽的微风吹过~~~~

当然啦,除了上述的两种处理程序方法还另外提供了 .all()、race()两个类方法。

这里简单说明一下这两个方法的使用场景,官方用法

.all()方法是接受一个数组作为参数,数组的元素是Promise实例对象,当参数中的实例对象的状态都为fulfilled时,Promise.all( )才会有返回,可用于 但所有的请求完成以后再进行某一个动作。

.race()方法它的参数要求跟Promise.all( )方法一样,不同的是,它参数中的promise实例,只要有一个状态发生变化(不管是成功fulfilled还是异常rejected),它就会有返回,其他实例中再发生变化,它也不管了,可用于选取连接最快的服务器等。

Listener:这么看来的确不错,兼容型怎么样呢?

Speaker:Promise从ES6提出,主流的浏览器和js环境基本都支持了Promise的特性,目前使用越来越广泛。

好了,我们很明显的看出了Promise的写法改进了嵌套关系的回调函数,变成了then连接链式步骤,增强了可读性,异常操作也提到了最外层。

Listener:可是,我还有一点疑惑,catch只是在不能运行then()方法的前提下,才会运行,可要是在then方法里面有了错误呢?怎么捕捉错误呢?

Speaker:你总是那么的懂我,坐下来由我show!

4、Promise与Generator合体

老规矩,先解释一波Generator。官方解释。中文名:生成器,生成什么?其实它也是一个再普通不过的函数,只是头上有“犄角”。一只犄角是:function关键字与函数名之间有一个星号;另一只犄角是:函数体内部使用yield表达式,定义不同的内部状态。简单理解就是:Generator函数类似一个状态机(依次存放每一个执行步骤),封装了多个内部状态(执行步骤)。执行Generator函数会返回一个可迭代的对象,遍历该对象可得到 Generator 函数内部的每一个状态(每一个执行步骤),进而生成状态(得到每一个执行步骤的数值)。这个状态(执行步骤)是由yield和最后的return来声明,由next往下执行转向生成下一个状态(执行步骤),不懂没关系,上码

那现在有了这个状态捕捉神器,还担心Promise函数那then()方法里面的异常无法捕捉吗,完全不用~~"码上"双双合体

promise与generator结合后,优化了语义化、也有了内部错误处理、代码风格更接近于同步代码的形式,是不是觉得完美啦,那你就错了,没有最好,只有更好,这样合并多麻烦呀,用之前还要先创建一个生成器函数,要是能把他们封装起来就好啦~~

Listener:

Speaker:先别这样说!重头戏在最后。

5、ES7提出的async函数

简单来说,Async就是Gnerator函数的语法糖。官方说明。代码奉上。

和promise与generator合体的代码相比,await代替了yield、生成器函数真的变成了没有犄角的普通函数。async用于定义一个异步函数,该函数返回一个Promise。他需要await来等待这个返回值。这样解释比较白话,还是看代码。async函数返回一个Promise,上面的代码其实是这样:

我们直接调用getBlogs().then()方法可获取async函数返回值。

如果async函数返回的是一个同步的值,这个值将被包装成一个理解resolve的Promise,等同于return Promise.resolve(value)。await用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await也可以用于一个同步的值。

对于async的异常,是直接在async函数中抛出一个异常,由于返回的是一个Promise,因此,这个异常可以调用返回Promise的catch()方法捕捉到。

可以看出写法更加简介明确,错误处理方式和Generator合体函数一样,均是通过对返回的Promise对象执行,使用他的catch方法。

Speaker:到此就结束啦,由最开始的地狱回调(可读性差)到后来的Promise(可读性增强但异常处理不是很好),又到后来的Generator函数(可以获得每一个执行步骤)来处理Promise存在的不可更好处理异常问题,再到最后的async函数,其实,不仅仅是学会了更好的实现异步方式,还有一个编程思想,增强自己的代码的可读性,给自己读懂机会,给别人读懂机会;容错性,易于后期的调试。

好啦,总结内容见下图:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181112G1ER7A00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券