JavaScript异步流程控制的前世今生

使用过js的开发者都知道JavaScript是单线程的,所谓的单线程是一次只能完成一种任务,若还有其他任务,需等待前一个任务执行完成后再来执行该任务。那么问题来了,假设我需要同时执行多个任务呢,单线程又如何能满足这种需求呢?

js引入了同步和异步,所谓同步就是上述所说的,另一个任务需要等待当前任务完成之后再执行,而异步呢指的是当前任务有一个或多个回调函数,当该任务执行完会触发回调函数的执行,而后一个任务也不必等待前一个任务执行完再执行,它的执行与前一个任务是否执行完无关。

我们常常遇到的就是浏览器的ajax请求,它是一种耗时操作,在该浏览器向服务器发送请求数据时,不必等待服务器返回数据,也可以做一些其它的操作或者任务,等服务器返回数据,会触发一个回调函数通知数据已经被请求回来了。

接下来我们通过一个例子一起来看下关于js异步流程的前世今生:

假设有3个小球A,B,C,A球在4ms内向右移动100px,接着是B球以同样时间,移动同样的距离,接着是C。

回调函数

以下给出部分实现的关键代码,这是一种很常规的使用回调函数来解决异步流程的问题,先执行完小球A,然后执行小球A成功的回调函数,一层一层依次嵌套,直至小球C。

    doSport() {

        // 1.回调

        this.move(this.ref1, 100, () => {

            this.move(this.ref2, 100, () => {

                this.move(this.ref3, 100, () => {

                    console.log('stop sport');

                })

            })

        })

    } 

    move(ele, target, callback) {

        let that = this;

        return new Promise(function(resolve, reject){

            let count = 0;

            let timer = setInterval(() => {

                if (count < target) {

                    ele.current.style.transform = `translateX(${++count}px)`;

                } else {

                    clearInterval(timer);

                    resolve();

                    callback && callback();

                }

            }, 4);

        })

    }

可以看出上述代码存在一些问题,如果有10个还好,20个呢甚至100个小球呢?那么是不是会造成代码地狱呢,层级嵌套太深,虽然可读性好,但是非常难维护,审美也很差。

 Promise

于是有了Promise的实现方式,通过then方法执行上述回调函数。

    doSport() {

        // 2.promise

        this.move(this.ref1, 100).then(() => {

            this.move(this.ref2, 100).then(() => {

                this.move(this.ref3, 100).then(() => {

                    console.log('stop sport');

                })

            })

        });

    } 

    move(ele, target, callback) {

        let that = this;

        return new Promise(function(resolve, reject){

            let count = 0;

            let timer = setInterval(() => {

                if (count < target) {

                    ele.current.style.transform = `translateX(${++count}px)`;

                } else {

                    clearInterval(timer);

                    resolve();

                    callback && callback();

                }

            }, 4);

        })

    }

move方法返回一个promise,通过then方法执行它的下一步操作。promise是CommonJS工作提出的一种规范,目的是为异步编程提供统一接口,它的思想是,每一个异步任务返回一个promise,then方法处理下一步的操作,成功态执行resolve函数,失败态就执行reject函数。

Generator

接着提出了Generator函数,在函数名前加上*,内部函数结合yield,当然最好的是搭配上co函数,该函数是可以帮你自动执行迭代器,从下面的代码我们可以看到通过用yield的异步方式实现同步操作,结合co返回一个promise,最后通过then方法执行任务成功的函数。

    // 3.generator

*doSport() {

        yield this.move(this.ref1, 100);

        yield this.move(this.ref2, 100);

        yield this.move(this.ref3, 100);

    } 

    // co:自动执行完迭代器

    co(it) {

        return new Promise(function(resolve, reject) {

            function next(d) {

                let { value, done } = it.next(d);

                if (!done) {

                    value.then(function(data) {

                        next(data)

                    }, reject);

                } else {

                    resolve(value)

                }

            } 

            next()   

        })

    }

    co(doSport()).then(function() {

        alert('ok');

    })

    move(ele, target, callback) {

        let that = this;

        return new Promise(function(resolve, reject){

            let count = 0;

            let timer = setInterval(() => {

                if (count < target) {

                    ele.current.style.transform = `translateX(${++count}px)`;

                } else {

                    clearInterval(timer);

                    resolve();

                    callback && callback();

                }

            }, 4);

        })

    }

由于generator函数需要搭配co函数,要手动的执行内部的next方法,而且不够直观,没有语义化。

Async/await

es7推出了Async/await的新语法,而且babel也支持,用async修饰函数,内部结合使用await,这种方式我个人觉得很优雅,很容易使用,其实它的内部实现也是gennerator

    // 4.async/await

    async doSport() {

        await this.move(this.ref1, 100);

        await this.move(this.ref2, 100);

        await this.move(this.ref3, 100);

    }

    move(ele, target, callback) {

        let that = this;

        return new Promise(function(resolve, reject){

            let count = 0;

            let timer = setInterval(() => {

                if (count < target) {

                    ele.current.style.transform = `translateX(${++count}px)`;

                } else {

                    clearInterval(timer);

                    resolve();

                    callback && callback();

                }

            }, 4);

        })

    }

async函数可以看做多个异步操作,包装成一个promise对象,而await就是内部then方法的语法糖,相比较于gennerator函数来说,async/await函数更加直观,语义化,而且更加好用,不需要co函数。

总结

总的来说,不同的异步方案都是解决异步流程的方式,但是异步编程的本质并没有变,只是对于开发者来说更加的友好,易于维护。时代在发展,技术也在日益更新,我们更重要的是要掌握背后的编程思想原理,这样才能更上一层楼。

你可能感兴趣的:(JavaScript异步流程控制的前世今生)