一、前言

异步编程对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是最好的办法。当然为了更加深刻的理解这些异步解决办法,一定要多多的用到项目中,多用才会多理解。

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

  1. js异步解决方案 --- 回调函数 vs promise vs generater/yield vs async/await

    javascript -- 深度解析异步解决方案 高级语言层出不穷, 然而唯 js 鹤立鸡群, 这要说道js的设计理念, js天生为异步而生, 正如布道者朴灵在 node深入浅出--(有兴趣的可以读一 ...

  2. JS 异步编程的解决方案,以及回调地狱的解决方案

    1.回调函数 回调函数是异步编程最基本的方法. 所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数. fs.readFile('/etc/fstab', ...

  3. 【JavaScript】【5】定时器(包含回调函数与Promise)

    文章目录 前言 一.回调函数 二. Promise promise对象 Promise对象的生成 加载图片写成一个Promise 三.定时器与清除定时器的方法 四.京东购物车倒计时案例 五.发送验证码 ...

  4. ES6/7 异步编程学习笔记

    前言 在ES6的异步函数出现之前,Js实现异步编程只有settimeout.事件监听.回调函数等几种方法 settTmeout 这种方法常用于定时器与动画的功能,因为其本质上其实是浏览器的WebAPI ...

  5. rust异步编程--理解并发/多线程/回调/异步/future/promise/async/await/tokio

    1. 异步编程简介 通常我们将消息通信分成同步和异步两种: 同步就是消息的发送方要等待消息返回才能继续处理其它事情 异步就是消息的发送方不需要等待消息返回就可以处理其它事情 很显然异步允许我们同时做更 ...

  6. JavaScript异步编程(1)- ECMAScript 6的Promise对象

    JavaScript的Callback机制深入人心.而ECMAScript的世界同样充斥的各种异步操作(异步IO.setTimeout等).异步和Callback的搭载很容易就衍生"回调金字 ...

  7. STM32F103ZE单片机在WWDG窗口看门狗的EWI中断中喂狗导致系统复位的原因及解决办法(中断函数重入问题)

    版权声明:本文为博主原创文章,欢迎转载    https://blog.csdn.net/ZLK1214/article/details/78308058 程序开启了WWDG的Early wakeup ...

  8. JavaScript封装异步函数 —— 【异步编程】 —— 如何获取一个函数中异步操作的结果?

    我们在写项目的时候有没有遇到这样的一个情况:一个函数里,嵌套着另一个异步函数,那如何在外面获取这个异步函数的结果呢?(return值)        就比如这样一个例子: function fn() ...

  9. 七十一、Vue项目城市选择页搜索逻辑实现,边输入边搜索功能的解决办法:节流函数

    2020/10/30. 周五.今天又是奋斗的一天. @Author:Runsen 写在前面:我是「Runsen」,热爱技术.热爱开源.热爱编程.技术是开源的.知识是共享的.大四弃算法转前端,需要每天的 ...

  10. C++多线程编程(3) 异步操作类 std::future std::promise std::async

    C++中提供了异步操作相关的类: 1. std::future: 异步结果的传输通道,可以很方便的获取线程函数的返回值. 在C++中,如果希望获取线程函数的返回值,就不能直接通过thread.join ...

最新文章

  1. Hibernate 具体用法(自整理)
  2. 常见的java异常_浅谈十个常见的Java异常出现原因
  3. Boost.python 编译和使用
  4. 关于在ubuntu下配置AMD显卡驱动的总结
  5. 2021年,Azure云遇到. NET5,注定开启高光时刻,微软的心,真大!
  6. 深度学习的1000+篇文章总结
  7. C++ HOOK实现全局键盘钩子的详细过程
  8. butter滤波器是iir吗_IIR Butterworth型模拟低通滤波器设计原理
  9. 计算机id和密码忘了怎么办,苹果平板电脑忘了ID号和密码怎么办?
  10. linux把m4s格式转换mp4,m4s格式(B站m4s怎么转换成MP4)
  11. 地坛——我的最爱 (2006-11-12 09:33:18)
  12. 京东供应商协同平台 客户评价数据导出python
  13. Pytorch+LSTM+Encoder+Decoder实现Seq2Seq模型
  14. 物联网卡零售应用的真实案例
  15. 小型餐饮管理系统(c++/win32 SDK/MYSQL 数据库)
  16. 问题解决:The connection to the server xxxxx:6443 was refused - did you specify the right host or port?
  17. Elasticsearch:Elasticsearch 查询示例 - 动手练习(一)
  18. Prodigal-原核生物基因预测
  19. 19-你知道哪几种锁?分别有什么特点?
  20. AMA分享回顾丨镜像网络MW对于分布式存储底层系统的思考和后续发展的看法

热门文章

  1. 二_单元测试和代码覆盖率
  2. HashMap的key可以是可变的对象吗???
  3. 全新的membership框架Asp.net Identity(2)——绕不过的Claims
  4. ReportView动态加载带参数的RDCL文件
  5. 1.Thinkphp入门--框架介绍
  6. bzoj:2141: 排队
  7. header简单用处
  8. 个人知识管理系统Version1.0开发记录(07)
  9. nyoj_49_开心的小明_201403161133
  10. 利用IE的滤镜解决IE6下PNG图片透明BUG