最近团队开始越来越多的使用es7标准的async/await,从最开始的promise到后面的generator,再到现在async,对于异步,每个时期都有着其特有的解决方案,今天笔者就以自己的接触为线索,简单的回顾一下其发展。

  众所周知,js的事件处理模型决定了它内部很多行为都是异步的,最常见的如setTimeout、setInterval、我们通常的ajax,当然还有我们的事件,代码如:

  1. dom.addEventListener('keydown', function(e){
  2. console.log(e);
  3. })

  这就是一段普通的键盘捕获程序,这本身当然是没什么问题的。有问题的是随着业务越来越复杂,我们需要不断的借助异步的方式处理各种各样的逻辑,然后代码就变成了这样:

  1. ajax('requestA', function(resA){
  2. //do sth
  3. ajax('requestB', function(resB){
  4. //do sth
  5. ajax('requestC', function(resC){
  6. //do sth
  7. ajax('requestD', function(resD){
  8. //do sth
  9. ajax('requestE', function(resE){
  10. //do sth
  11. ajax('requestF', function(resF){
  12. //do sth
  13. ajax('requestG', function(resG){
  14. //do sth
  15. ajax('requestH', function(resH){
  16. //do sth
  17. })
  18. })
  19. })
  20. })
  21. })
  22. })
  23. })
  24. })

  当然,这也就是我们常说的回调地狱(callback hell)。正因为出现了这样一种可读性很差的代码结果,在ES6初期便退出了promise来解决这一“怪异”的问题,先来看看promise的基本语法,形如:

  1. new Promise((resolve, reject) => {
  2. if(/*处理结果*/){
  3. reslove()
  4. }else{
  5. reject();
  6. }
  7. }).then(()=>{
  8. successCallback()
  9. }).catch(()=>{
  10. failCallback()
  11. })

  常见的promise的用法就是这样,当然还有诸如Promise.all等方法就不在这里展开了,接着我们看看用promise重构一下上面的回调地狱会变成什么样子:

  1. let resA = new Promise((resolve, reject) => {
  2. ajax('requestA', function(res){
  3. reslove(res)
  4. })
  5. });
  6. let resB = new Promise((resolve, reject) => {
  7. ajax('requestB', function(res){
  8. reslove(res)
  9. })
  10. });
  11. let resC = new Promise((resolve, reject) => {
  12. ajax('requestC', function(res){
  13. reslove(res)
  14. })
  15. });
  16. let resD = new Promise((resolve, reject) => {
  17. ajax('requestD', function(res){
  18. reslove(res)
  19. })
  20. });
  21. let resE = new Promise((resolve, reject) => {
  22. ajax('requestE', function(res){
  23. reslove(res)
  24. })
  25. });
  26. let resF = new Promise((resolve, reject) => {
  27. ajax('requestF', function(res){
  28. reslove(res)
  29. })
  30. });
  31. let resG = new Promise((resolve, reject) => {
  32. ajax('requestG', function(res){
  33. reslove(res)
  34. })
  35. });
  36. let resH = new Promise((resolve, reject) => {
  37. ajax('requestH', function(res){
  38. reslove(res)
  39. })
  40. });
  41.  
  42. resA.then((resA)=>{
  43. //do sth
  44. resB.then((resB)=>{
  45. //do sth
  46. resC.then((resC)=>{
  47. //do sth
  48. resD.then((resD)=>{
  49. //do sth
  50. resE.then((resE)=>{
  51. //do sth
  52. resF.then((resF)=>{
  53. //do sth
  54. resG.then((resG)=>{
  55. //do sth
  56. resH.then((resH)=>{
  57. //do sth
  58. })
  59. })
  60. })
  61. })
  62. })
  63. })
  64. })
  65. })

  理想很美好,但是现实似乎并不尽如人意,不过因为promise的产生主要针对的是回调函数剥夺了我们使用return和throw关键字的能力(比如try-catch不能对异步操作这种机制,不过上面这个例子由于太简略,连一个catch都没有。。),所以要完全取代回调我们还要往前走一步,使用generator,照例我们先看看generator的语法: 

  1. function* gen(){
  2. let res = 0;
  3. yield res++;
  4. yield res++;
  5. yield res++;
  6. }
  7. let myGen = gen();
  8. console.log(myGen.next().value);  //0
  9. console.log(myGen.next().value);  //1
  10. console.log(myGen.next().value);  //2

  其实语法也很简单,主要就是用“*”修饰了function,然后在内部使用yield关键字,构造了一种惰性调用的语境,然后我们可以将之前的callback hell代码改造为:

  1. function* Ajax(){
  2. let resA = yield new Promise((resolve, reject) => {
  3. ajax('requestA', (res) =>{
  4. resolve(res);
  5. })
  6. });
  7. //dosth
  8. let resB = yield new Promise((resolve, reject) => {
  9. ajax('requestB', (res) =>{
  10. resolve(res);
  11. })
  12. });
  13. //dosth
  14. let resC = yield new Promise((resolve, reject) => {
  15. ajax('requestC', (res) =>{
  16. resolve(res);
  17. })
  18. });
  19. //dosth
  20. let resD = yield new Promise((resolve, reject) => {
  21. ajax('requestD', (res) =>{
  22. resolve(res);
  23. })
  24. });
  25. //dosth
  26. let resE = yield new Promise((resolve, reject) => {
  27. ajax('requestE', (res) =>{
  28. resolve(res);
  29. })
  30. });
  31. //dosth
  32. let resF = yield new Promise((resolve, reject) => {
  33. ajax('requestF', (res) =>{
  34. resolve(res);
  35. })
  36. });
  37. //dosth
  38. let resG = yield new Promise((resolve, reject) => {
  39. ajax('requestG', (res) =>{
  40. resolve(res);
  41. })
  42. });
  43. //dosth
  44. let resH = yield new Promise((resolve, reject) => {
  45. ajax('requestH', (res) =>{
  46. resolve(res);
  47. })
  48. });
  49. }
  50.  
  51. co(Ajax)

  这么看起来,似乎确实整个代码变得“同步”化了,虽然还要借助下co,不过这种写法因为要在外面包裹generator,通常结合koa在node端使用得比较多。但是这似乎仍然不能完全满足我们的需求,毕竟generator其实作为生成器,虽然能够满足我们同步请求的功能,但是它被创造的初衷似乎并不是单纯只干这事儿的,(它的产生原本是为了js的惰性求值功能)于是,到了ES7我们迎来了新的关键字async/await:

  1. async function Ajax(){
  2. async function _ajax(url){
  3. return new Promise((resolve, reject) => {
  4. ajax(url, (res)=>{
  5. resolve(res)
  6. })
  7. });
  8. }
  9.  
  10. let resA = await _ajax('requestA');
  11. //do sth
  12. let resB = await _ajax('requestB');
  13. //do sth
  14. let resC = await _ajax('requestC');
  15. //do sth
  16. let resD = await _ajax('requestD');
  17. //do sth
  18. let resE = await _ajax('requestE');
  19. //do sth
  20. let resF = await _ajax('requestF');
  21. //do sth
  22. let resG = await _ajax('requestH');
  23. //do sth
  24. let resH = await _ajax('requestG');
  25. }
  26. Ajax();

  它与generator的写法类似,需要在function前面加上关键字async,然后在里面通过await的方式显示调用,于是,再最小程度的修改我们代码的基础上,我们完成了将异步调用变为同步调用的转换,一切变得那么的和谐~

  但是,毕竟浏览器厂商还有个更新同步,替换的过程,所以我们正常工作中会碰到很多情况需要使用polyfill的情况,笔者也颇有点好奇的async/await的polyfill的内部实现,我们都知道,babel的polyfill中对promise实现是基于while循环实现的,而且还需要自己手动引用,而generator也采用了相似的实现:

  1. //源码
  2. function* fn(){
  3. setTimeout(()=>console.log('hello generator'), 1000);
  4. }
  5. //babel transform后
  6. 'use strict';
  7.  
  8. var _marked = [fn].map(regeneratorRuntime.mark);
  9.  
  10. function fn() {
  11. return regeneratorRuntime.wrap(function fn$(_context) {
  12. while (1) {
  13. switch (_context.prev = _context.next) {
  14. case 0:
  15. setTimeout(function () {
  16. return console.log('hello generator');
  17. }, 1000);
  18.  
  19. case 1:
  20. case 'end':
  21. return _context.stop();
  22. }
  23. }
  24. }, _marked[0], this);
  25. }

  可以看出,其实主要依然是使用while。。而且还是while(1),而async/await也是惊人的相似:

  1. //源码
  2. async function fn(){
  3. setTimeout(()=>console.log('hello async'), 1000)
  4. }
  5. //bebal transform 后
  6. 'use strict';
  7.  
  8. var fn = function () {
  9. var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
  10. return regeneratorRuntime.wrap(function _callee$(_context) {
  11. while (1) {
  12. switch (_context.prev = _context.next) {
  13. case 0:
  14. setTimeout(function () {
  15. return console.log('hello async');
  16. }, 1000);
  17.  
  18. case 1:
  19. case 'end':
  20. return _context.stop();
  21. }
  22. }
  23. }, _callee, this);
  24. }));
  25.  
  26. return function fn() {
  27. return _ref.apply(this, arguments);
  28. };
  29. }();
  30.  
  31. function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

  虽然与generator不同,在最外层还用asyncToGenerator包装了一下,不过。。核心的while循环依然存在。。

  想来也是蛮有些讽刺的,为了解决一个问题,业界想出的三套方案,到最终,居然是依靠一个在我们写代码之初便不推荐使用的一种“死循环”的方式来达成的,虽然浏览器底层不会真这么实现,但是每每想到自己的代码经过babel编译后,会是这么一个样子,心里还是隐隐有些担忧的。

  想来再结合笔者最近看到的一些历史中的轶事,也颇是觉得其中微妙之处,当有亲身经历者,方可体会的感触。时代的浪潮都在滚滚向前,但愿迎接我们的是新升的朝阳,而非一个漫长的黑夜。

【js】callback时代的变更的更多相关文章

  1. js callback 和 js 混淆

    function test(a,callback){ a+=100; callback(a) } function abc(a){ a+=100; alert(a); } test(5,abc) js ...

  2. js callback函数

    A callback is a function that is passed as an argument to another function and is executed after its ...

  3. javascript 自己主动绑定JS callback 的方法函数

    自己写的一个javascript 智能绑定callback 而且调用运行的函数.主要用于异步请求的 ajax中: <!DOCTYPE html> <html> <head ...

  4. callbag js callback 标准-支持轻量级观测以及迭代

    callbag 是一个js 的回调标准,方便开发支持观测以及迭代的代码 类似已经有好多的实现了 callbag-basics 比rxjs 以及xstream 还快 wonka 说明 基于标准的开发,对 ...

  5. js callback回调的一种写法

    getLocation.cityname(latitude, longitude, function (data1) { SetCityCallBack(data1); }); 定义方法: var g ...

  6. js url?callback=xxx xxx的介绍

    由于安全的原因,浏览器做了很多方面的工作,由此也就引入了一系列的跨域问题,需要注意的是: 跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了.最好的例子是 CSRF ...

  7. 为什么返回的数据前面有callback? ashx/json.ashx?的后面加 callback=? 起什么作用 js url?callback=xxx xxx的介绍 ajax 跨域请求时url参数添加callback=?会实现跨域问题

    为什么返回的数据前面有callback?   这是一个同学出现的问题,问到了我. 应该是这样的: 但问题是这样的: 我看了所请求的格式和后台要求的也是相同的.而且我也是这种做法,为什么他的就不行呢? ...

  8. vue—你必须知道的 js数据类型 前端学习 CSS 居中 事件委托和this 让js调试更简单—console AMD && CMD 模式识别课程笔记(一) web攻击 web安全之XSS JSONP && CORS css 定位 react小结

    vue—你必须知道的   目录 更多总结 猛戳这里 属性与方法 语法 计算属性 特殊属性 vue 样式绑定 vue事件处理器 表单控件绑定 父子组件通信 过渡效果 vue经验总结 javascript ...

  9. Ext JS 如何动态加载JavaScript创建窗体

    JavaScript不需要编译即可运行,这让JavaScript构建的应用程序可以变得很灵活.我们可以根据需要动态从服务器加载JavaScript脚本来创建和控制UI来与用户交互.下面结合Ext JS ...

随机推荐

  1. 修改phpMyAdmin导入SQL文件的大小限制

    用phpMyAdmin导入mysql数据库时,我的10M的数据库不能导入,提示mysql数据库最大只能导入2M. phpMyAdmin数据库导入出错: You probably tried to up ...

  2. noip 2011

    铺地毯 题目描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯.一共有 n 张地毯,编号从 1 到n .现在将这些地毯按照编号从小到大的顺 ...

  3. Codeforces Educational Round 23

    A emmmmmmmmm B emmmmmmmmm C(套路) 题意: 给定n和s(n,s<=1e18),计算n以内有多少个数x满足(x-x的各个位置数字之和)>=s 分析: 容易想到如果 ...

  4. MySQL使用教程收集(语法教程/命令教程)

    说明:现在市面上的教程除了基本语法外,都基本是五花八门的,最权威且最全面的解释应该上官网去查看. https://www.tutorialspoint.com/mysql/index.htm http ...

  5. 我的arcgis培训照片6

    来自:http://www.cioiot.com/successview-556-1.html

  6. linux man 1,2,3 命令

    原文: http://blog.sina.com.cn/s/blog_969c52730101c0p7.html ------------------------------------------- ...

  7. SDUT--找朋友(BFS&amp;&amp;DFS)

    找朋友 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描写叙述 X,作为户外运动的忠实爱好者,总是不想呆在家里.如今,他想把死宅Y从家 ...

  8. HDMI接口基础知识及硬件设计

    参考资料:http://blog.csdn.net/u013625961/article/details/53434189: http://blog.csdn.net/u014276460/artic ...

  9. Tomcat 隐藏Server Name

    隐藏Http请求中的Header ServerName 方法一 在tomcat/lib/tomcat-coyote.jar中 下面两个文件 org/apache/coyote/http11/Const ...

  10. hdu1078 FatMouse and Cheese(记忆化搜索)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1078 题目大意: 题目中的k表示横向或者竖直最多可曾经进的距离,不可以拐弯.老鼠的出发点是(1,1) ...