资讯专栏INFORMATION COLUMN

ES6(一) —— 异步编程解决办法[从回调函数到promise,generator,async]

greatwhole / 902人阅读

摘要:回调函数这是最原始的一种异步解决方法。从的对象演化而来对象是提出的一种对异步编程的解决方案,但它不是新的语法,而是一种新的写法,允许将回调函数的嵌套改成链式调用。

一、前言

异步编程对JavaScript来说非常重要,因为JavaScript的语言环境是单线程的,如果没有异步编程将变得非常可怕,估计根本无法使用。这篇文章就来总结一下从最原始的回调函数到现在的ES6、ES7的新方法。

文章并不会具体介绍每种方法的原理,如果不是特别懂需要详细了解的同学可以看阮一峰的ES6入门。阮大大介绍得非常具体,从原理到用法。

- 什么是单线程?

单线程就是指进程中只有一个线程。单线程执行程序时,按照代码的顺序,上一个任务完成后才会执行下一个任务。同一个时间只做一件事情。

- 为什么JavaScript是单线程的?

JavaScript的主要作用就是操作DOM,如果两段JS同时操作一个DOM,会引起渲染的冲突。所以JavaScript只能是单线程的。
HTML5中提出的Web Worker,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

- 什么是异步?

同步是指任务一件一件的按顺序完成,上一件没有完成就无法做下一件;而异步则是指,开始做一件事之后就放在那儿等待结果,不需要守着,继续做下一件事即可。

异步可以解决JavaScript单线程效率低、同一事件只能做一件事情的问题。

console.log(1);
setTimeOut(()=>{
    console.log(2);
},1000)
console.log(3);

这段代码首先会打印1,然后打印3,过1000ms以后打印2。

然而这段代码内部是如何运行的呢,打印1和打印3的命令是同步命令,所以直接按顺序放到主进程中执行,setTimeOut里的是一个异步命令,在1000ms以后会被放入异步队列中。而主进程会通过事件循环(event loop)不断地从异步队列中取出命令,然后执行。当主进程在1000ms以后查询到了打印2的命令时,便把这个函数拿到主进程中执行。

二、异步编程的解决办法

这里全部用ajax连续调用例子,接口是豆瓣的真实接口,可以得到具体的数据,但有限制每小时150次。

1.回调函数

这是最原始的一种异步解决方法。回调函数,就是指一件事做完以后,拿到结果后要做的事情。

var urlBase = "https://api.douban.com/";
var start = 0,count = 5;
$.ajax({
    url: urlBase+"v2/book/user/1219073/collections",
    type: "GET",
    dataType: "jsonp",
    data:{
        start:start,
        count:count
    },
    success: function(data){
        console.log(data);
        start+=count;
        $.ajax({
            url: urlBase+"v2/book/user/1219073/collections",
            type: "GET",
            dataType: "jsonp",
            data:{
                start:start,
                count:count
            },
            success:function(data){
                console.log(data);
                start+=count;
                $.ajax({
                    url: urlBase+"v2/book/user/1219073/collections",
                    type: "GET",
                    dataType: "jsonp",
                    data:{
                        start:start,
                        count:count
                    },
                    success:function(data){
                        console.log(data);
                    }
                })
            }
        })
    }
})

这是用jquery的ajax方法调用的豆瓣某个人的收藏的图书,start和count是豆瓣提供的接口参数,start代表从哪一条数据开始获取,count代表一共获取多少条数据。

从上面的代码可以看到多个回调函数的嵌套,如果需要调用得越多,回调也堆积得越多,多了以后代码就很难维护,时间久了自己也要花很久才能看懂代码。

改进办法

将每一次回调的方法封装成函数,代码量会减少很多。

var urlBase = "https://api.douban.com/";
var start = 0,count = 5;
function ajax(start,count,cb){
    $.ajax({
        url: urlBase+"v2/book/user/1219073/collections",
        type: "GET",
        dataType: "jsonp",
        data:{
            start:start,
            count:count
        },
        success:function(data){
            console.log(data);
            start+=count;
            cb && cb(start);
        }
    })

}

ajax(start,count,function(start){
    ajax(start,count,function(start){
        ajax(start,count)
    })
});

但是这样依然没有解决“回调地狱”的问题,当每次回调的逻辑操作变得越来越多的时候,代码依然难以维护。

2.Promise(从jQuery的deferred对象演化而来)

Promise对象是ES6提出的一种对异步编程的解决方案,但它不是新的语法,而是一种新的写法,允许将回调函数的嵌套改成链式调用。

虽然说Promise是ES6提出的标准,但其实jQuery在1.5版本以后就提出了类似的东西,叫做deferred对象。具体学习可以看jQuery的deferred对象详解。

const urlBase = "https://api.douban.com/";
let start = 0,count = 5;
function ajax(start,count){
    let dtd = $.Deferred();
    
    $.ajax({
        url: urlBase+"v2/book/user/1219073/collections",
        type: "GET",
        dataType: "jsonp",
        data:{
            start:start,
            count:count
        },
        success:function(data){
            start+=count;
            dtd.resolve(data,start);
        },
        error:function(err){
            dtd.reject(err);
        }
    })

    return dtd;
}


ajax(start,count).then((data1,start) => {
    console.log(data1);
    return ajax(start,count);
}).then((data2,start) => {
    console.log(data2);
    return ajax(start,count);
}).then((data3,start) => {
    console.log(data3);
}).catch((err) => {
    console.log("这里出错啦");
})

从这段代码可以看出来,写法和promise非常相似了,可以猜测promise就是从deferred演化而来的。

同样的功能实现可以改成以下写法:

const urlBase = "https://api.douban.com/";
let start = 0,count = 5;
function ajax(start,count){
    return new Promise(function(resolve,reject){
        $.ajax({
            url: urlBase+"v2/book/user/1219073/collections",
            type: "GET",
            dataType: "jsonp",
            data:{
                start:start,
                count:count
            },
            success:function(data){
                start+=count;
                resolve(data,start);
            },
            error:function(err){
                reject(err);
            }
        })
    })
}


ajax(start,count).then((data1,start) => {
    console.log(data1);
    return ajax(start,count);
}).then((data2,start) => {
    console.log(data2);
    return ajax(start,count);
}).then((data3,start) => {
    console.log(data3);
}).catch((err) => {
    console.log("这里出错啦");
})

Promise使用.then方法解决了回调的问题,但代码依然冗余,且语义不强,放眼望去全是.then方法,很难找出需要修改的地方。

3.Generator

Generator函数也是ES6中提出的异步编程解决方法,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。最大特点就是可以交出函数的执行权(即暂停执行)。
异步操作需要暂停的地方,都用yield语句注明。

const urlBase = "https://api.douban.com/";
let start = 0,count = 5;
function ajax(start,count){
    return new Promise(function(resolve,reject){
        $.ajax({
            url: urlBase+"v2/book/user/1219073/collections",
            type: "GET",
            dataType: "jsonp",
            data:{
                start:start,
                count:count
            },
            success:function(data){
                start+=count;
                resolve(data);
            },
            error:function(err){
                reject(err);
            }
        })
    })
    
}

let gen = function*(){
    yield ajax(start,count);
    start+=count;
    yield ajax(start,count);
    start+=count;
    yield ajax(start,count);
}


let g = gen();
g.next().value.then((data1) => {
    console.log(data1);
    g.next().value.then((data2) => {
        console.log(data2);
        g.next().value.then((data3) => {
            console.log(data3);
        })
    })
})

这样在gen函数内三个ajax请求就看起来非常像同步的写法了,但是执行的过程并不清晰,且需要手动.next来执行下一个操作。这并不是我们想要的完美异步方案。

4.async

async函数是ES7提出的一种异步解决方案,它与generator并无大的不同,而且可以说它就是generator的一种语法糖。它的语法只是把generator函数里的*换成了async,yield换成了await,但它同时有几个优点。

(1)内置执行器。这表示它不需要不停的next来使程序继续向下进行。
(2)更好的语义。async代表异步,await代表等待。
(3)更广的适用性。await命令后面可以跟Promise对象,也可以是原始类型的值。
(4)返回的是Promise。

const urlBase = "https://api.douban.com/";
let start = 0,count = 5;
function ajax(start,count){
    return new Promise(function(resolve,reject){
        $.ajax({
            url: urlBase+"v2/book/user/1219073/collections",
            type: "GET",
            dataType: "jsonp",
            data:{
                start:start,
                count:count
            },
            success:function(data){
                start+=count;
                resolve(data);
            },
            error:function(err){
                reject(err);
            }
        })
    })
    
}

async function getData(){
    let data = null;
    try{
        for(let i = 0;i < 3;i++){
            data = await ajax(start,count);
            console.log(data);
            start+=count;
        }
    }
    catch(err){
        console.log(err);
    }
}

getData();

用async函数改写之后语义清晰,代码量也减少了,并且内部自带执行器,感觉很符合想象中的异步解决方法。

三、结语

到此就把几种常见的异步回调方法介绍完了,我个人感觉用async+promise是最好的办法。当然为了更加深刻的理解这些异步解决办法,一定要多多的用到项目中,多用才会多理解。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93361.html

相关文章

  • JavaScript异步编程的终极演变

    摘要:在诞生以前,异步编程的方式大概有下面四种回调函数事件监听发布订阅对象将异步编程带入了一个全新的阶段,中的函数更是给出了异步编程的终极解决方案。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。 写在前面 有一个有趣的问题: 为什么Node.js约定回调函数的第一个参数必须是错误对象err(如果没有错误,该参数就是null)? 原因是执行回调函...

    whjin 评论0 收藏0
  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • ES6中的异步编程Generators函数+Promise:最强大的异步处理方式

    摘要:更好的异步编程上面的方法可以适用于那些比较简单的异步工作流程。小结的组合目前是最强大,也是最优雅的异步流程管理编程方式。 访问原文地址 generators主要作用就是提供了一种,单线程的,很像同步方法的编程风格,方便你把异步实现的那些细节藏在别处。这让我们可以用一种很自然的方式书写我们代码中的流程和状态逻辑,不再需要去遵循那些奇怪的异步编程风格。 换句话说,通过将我们generato...

    Taonce 评论0 收藏0
  • 篇文章了解前端异步编程方案演变

    摘要:对于而言,异步编程我们可以采用回调函数,事件监听,发布订阅等方案,在之后,又新添了,,的方案。总结本文阐述了从回调函数到的演变历史。参考文档深入掌握异步编程系列理解的 对于JS而言,异步编程我们可以采用回调函数,事件监听,发布订阅等方案,在ES6之后,又新添了Promise,Genertor,Async/Await的方案。本文将阐述从回调函数到Async/Await的演变历史,以及它们...

    lmxdawn 评论0 收藏0
  • 细说JS异步发展历程

    摘要:参考文章珠峰架构课墙裂推荐细说异步函数发展历程异步编程谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和,您的肯定是我前进的最大动力。 知其然知其所以然,首先了解三个概念: 1.什么是同步? 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。此调...

    RiverLi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<