异步编程有哪几种方法来实现?

发布时间:2019-08-19 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了异步编程有哪几种方法来实现?脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

这里是修真院前端小课堂,每篇分享文从

【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】

八个方面深度解析前端知识/技能,本篇分享的是:

【异步编程有哪几种方法来实现?】

大家好,我是IT修真院武汉分院web第16期的学员孟晨,一枚正直纯洁善良的web程序员 今天给大家分享一下,修真院官网js(职业)任务五,深度思考中的知识点——异步编程有哪几种方法来实现?

 

1.背景介绍
你可能知道,Javascript语言的执行环境是"单线程"(single thread)。
所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

2.知识剖析
常用的异步编程的几种方法?
首先我们来看一个基本的例子,在这个例子中输出的顺序是1,3,2,我们想让他按顺序1,2,3输出就需要用到异步编程的方法

function fn1() {                                                                                     

    console.LOG('Function 1')                                                                  

}                                                                                                            

function fn2() {                                                                                     

    setTimeout(() => {                                                                        

        console.log('Function 2')                                                            

    }, 2000)                                                                                       

}                                                                                                           

function fn3() {                                                                                   

    setTimeout(() => {                                                                      

        console.log('Function 3')                                                         

    }, 500)                                                                                          

}                                                                                                       

fn1()                                                                                                 

fn2()                                                                                                 

fn3()                                                                                                 

// output =>                                                                                     

// Function 1                                                                                       

// Function 3                                                                                    

// Function 2                                                                                     

 

壹.回调函数

回调函数是异步编程的方法中最简单也是最常用的一个方法

采用这种方式,我们把同步操作变成了异步操作F1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

如果再嵌套多几层,代码会变得多么难以理解 这个被称之为“回调函数噩梦”(callback hell)!!!

也是可以看看例子,如果按照咱们刚刚的写法的话输出顺序会是3,2,1,所以把每个函数中写成回调函数的形式

就可以让执行完了前面的才会执行后面的,然后标红的部分就是被称为回调函数噩梦的原因

只是少数嵌套函数的话不明显但多层嵌套代码就会显得很混乱

 

function fn1(f) {

    setTimeout(() => {

        console.log('Function 1')

        f()

    }, 1000)

}

function fn2(f) {

    setTimeout(() => {

        console.log('Function 2')

        f()

    }, 2000)

}

function fn3() {

    setTimeout(() => {

        console.log('Function 3')

    }, 500)

}

fn1(function () {

    fn2(fn3)

})

// output =>

// Function 1

// Function 2

// Function 3

 

贰.事件发布/订阅

发布/订阅模式也是诸多设计模式当中的一种,恰好这种方式可以在es5下相当优雅地处理异步操作。什么是发布/订阅呢?以上一节的例子来说,fn1,fn2,fn3都可以视作一个事件的发布者,只要执行它,就会发布一个事件。这个时候,我们可以通过一个事件的订阅者去批量订阅并处理这些事件,包括它们的先后顺序。下面我们基于上一章节的例子,增加一个消息订阅者的方法(为了简单起见,代码使用了es6的写法):

class AsynCFunArr {

    constructor(...arr) {

        this.funcArr = [...arr]

    }

    next() {

        const fn = this.funcArr.shift()

        if (typeof fn === 'function') fn()

    }

 

    run() {

        this.next()

    }

}

const asyncFunArr = new AsyncFunArr(fn1, fn2, fn3)

function fn1() {

console.log('Function 1')

asyncFunArr.next()

}

function fn2() {

    setTimeout(() => {

        console.log('Function 2')

        asyncFunArr.next()

    }, 500)

}

function fn3() {

    console.log('Function 3')

    asyncFunArr.next()

}

// output =>

// Function 1

// Function 2

// Function 3

 

叁.PromISE对象

romises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。 简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:   f1().then(f2); 这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。 比如,指定多个回调函数:   f1().then(f2).then(f3); 再比如,指定发生错误时的回调函数:   f1().then(f2).fail(f3); 而且,它还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。这种方法的缺点就是编写和理解,都相对比较难。

标红处就是所谓的链式写法,这样写带来了结构清晰的好处

function fn1() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('Function 1')

            resolve()

        }, 1000)

    })

}

function fn2() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('Function 2')

            resolve()

        }, 2000)

    })

}

function fn3() {

    setTimeout(() => {

        console.log('Function 3')

    }, 500)

}

fn1()

.then(fn2)

.then(fn3)

// output =>

// Function 1

// Function 2

// Function 3

肆.GENERATOR

如果说Promise的使用能够化回调为链式,那么generator的办法则可以消灭那一大堆的PRomise特征方法,比如一大堆的then()。
generator函数asyncFunArr()接受一个待执行函数列表fn,异步函数将会通过yield来执行。在异步函数内,通过af.next()激活generator函数的下一步操作。
这么粗略的看起来,其实和发布/订阅模式非常相似,都是通过在异步函数内部主动调用方法,告诉订阅者去执行下一步操作。但是这种方式还是不够优雅,比如说如果有多个异步函数,那么这个generator函数肯定得改写,而且在语义化的程度来说也有一点不太直观。

function fn1() {

    setTimeout(() => {

        console.log('Function 1')

    }, 1000)

}

 

function fn2() {

    setTimeout(() => {

        console.log('Function 2')

    }, 2000)

}

 

function fn3() {

    setTimeout(() => {

        console.log('Function 3')

    }, 500)

}

 

function* asyncFunArr(...fn) {

    fn[0]()

    yield fn[1]()

    fn[2]()

}

const af = asyncFunArr(fn1, fn2, fn3)

af.next()

// output =>

// Function 1

// Function 2

// Function 3

伍.优雅的ASYNC/AWAIT

使用最新版本的Node已经可以原生支持async/await写法了,通过各种pollyfill也能在旧的浏览器使用。那么为什么说async/await方法是最优雅的呢?
有没有发现,在定义异步函数fn2的时候,其内容和前文使用Promise的时候一模一样?再看执行函数asyncFunArr(),其执行的方式和使用generator的时候也非常类似。
异步的操作都返回Promise,需要顺序执行时只需要await相应的函数即可,这种方式在语义化方面非常友好,对于代码的维护也很简单——只需要返回Promise并await它就好,无需像generator那般需要自己去维护内部yield的执行。

 

 function fn1() {

            return new Promise((resolve, reject) => {

                setTimeout(() => {

                    console.log('Function 1')

                    resolve()

                }, 3000)

            })

        }

        function fn2() {

            return new Promise((resolve, reject) => {

                setTimeout(() => {

                    console.log('Function 2')

                    resolve()

                }, 2000)

            })

        }

        function fn3() {

            setTimeout(() => {

                console.log('Function 3')

            }, 500)

        }

        async function asyncFunArr() {

            await fn1()

            await fn2()

            await fn3()

        }

        asyncFunArr()

        // output =>

        // Function 1

        // Function 2

        // Function 3

3.常见问题
何时使用异步

4.解决方案
在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

5.代码实战
6.拓展思考
异步的好处: 1、异步流程可以立即给调用方返回初步的结果。 
2、异步流程可以延迟给调用方最终的结果数据,在此期间可以做更多额外的工作,例如结果记录等等。 
3、异步流程在执行的过程中,可以释放占用的线程等资,避免阻塞,等到结果产生再重新获取线程处理。 
4、异步流程可以等多次调用的结果出来后,再统一返回一次结果集合,提高响应效率。

 

 

7.参考文献
谈一谈几种处理JavaScript异步操作的办法
Javascript异步编程的4种方法

8.更多讨论
鸣谢

感谢大家观看

BY : 孟晨

脚本宝典总结

以上是脚本宝典为你收集整理的异步编程有哪几种方法来实现?全部内容,希望文章能够帮你解决异步编程有哪几种方法来实现?所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。