博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【js】callback时代的变更
阅读量:6332 次
发布时间:2019-06-22

本文共 7525 字,大约阅读时间需要 25 分钟。

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

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

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

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

ajax('requestA', function(resA){    //do sth    ajax('requestB', function(resB){        //do sth        ajax('requestC', function(resC){            //do sth            ajax('requestD', function(resD){                //do sth                ajax('requestE', function(resE){                    //do sth                    ajax('requestF', function(resF){                        //do sth                        ajax('requestG', function(resG){                            //do sth                            ajax('requestH', function(resH){                                //do sth                            })                        })                    })                })                        })                })        })    })

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

new Promise((resolve, reject) => {    if(/*处理结果*/){        reslove()    }else{        reject();    }}).then(()=>{    successCallback()}).catch(()=>{    failCallback()})

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

let resA = new Promise((resolve, reject) => {    ajax('requestA', function(res){        reslove(res)    })});let resB = new Promise((resolve, reject) => {    ajax('requestB', function(res){        reslove(res)    })});let resC = new Promise((resolve, reject) => {    ajax('requestC', function(res){        reslove(res)    })});let resD = new Promise((resolve, reject) => {    ajax('requestD', function(res){        reslove(res)    })});let resE = new Promise((resolve, reject) => {    ajax('requestE', function(res){        reslove(res)    })});let resF = new Promise((resolve, reject) => {    ajax('requestF', function(res){        reslove(res)    })});let resG = new Promise((resolve, reject) => {    ajax('requestG', function(res){        reslove(res)    })});let resH = new Promise((resolve, reject) => {    ajax('requestH', function(res){        reslove(res)    })});resA.then((resA)=>{    //do sth    resB.then((resB)=>{        //do sth         resC.then((resC)=>{            //do sth            resD.then((resD)=>{                //do sth                resE.then((resE)=>{                    //do sth                    resF.then((resF)=>{                        //do sth                        resG.then((resG)=>{                            //do sth                            resH.then((resH)=>{                                //do sth                            })                        })                    })                })            })        })    })})

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

function* gen(){  let res = 0;  yield res++;  yield res++;  yield res++;}let myGen = gen();console.log(myGen.next().value);  //0console.log(myGen.next().value);  //1console.log(myGen.next().value);  //2

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

function* Ajax(){  let resA = yield new Promise((resolve, reject) => {      ajax('requestA', (res) =>{          resolve(res);      })      });  //dosth  let resB = yield new Promise((resolve, reject) => {      ajax('requestB', (res) =>{          resolve(res);      })      });  //dosth  let resC = yield new Promise((resolve, reject) => {      ajax('requestC', (res) =>{          resolve(res);      })      });  //dosth  let resD = yield new Promise((resolve, reject) => {      ajax('requestD', (res) =>{          resolve(res);      })      });  //dosth  let resE = yield new Promise((resolve, reject) => {      ajax('requestE', (res) =>{          resolve(res);      })      });  //dosth  let resF = yield new Promise((resolve, reject) => {      ajax('requestF', (res) =>{          resolve(res);      })      });  //dosth  let resG = yield new Promise((resolve, reject) => {      ajax('requestG', (res) =>{          resolve(res);      })      });  //dosth  let resH = yield new Promise((resolve, reject) => {      ajax('requestH', (res) =>{          resolve(res);      })      });}co(Ajax)

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

async function Ajax(){  async function _ajax(url){      return new Promise((resolve, reject) => {          ajax(url, (res)=>{              resolve(res)          })      });  }     let resA = await _ajax('requestA');  //do sth  let resB = await _ajax('requestB');  //do sth  let resC = await _ajax('requestC');  //do sth  let resD = await _ajax('requestD');  //do sth  let resE = await _ajax('requestE');  //do sth  let resF = await _ajax('requestF');  //do sth  let resG = await _ajax('requestH');  //do sth  let resH = await _ajax('requestG');}Ajax();

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

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

//源码function* fn(){  setTimeout(()=>console.log('hello generator'), 1000);}//babel transform后'use strict';var _marked = [fn].map(regeneratorRuntime.mark);function fn() {  return regeneratorRuntime.wrap(function fn$(_context) {    while (1) {      switch (_context.prev = _context.next) {        case 0:          setTimeout(function () {            return console.log('hello generator');          }, 1000);        case 1:        case 'end':          return _context.stop();      }    }  }, _marked[0], this);}

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

//源码async function fn(){  setTimeout(()=>console.log('hello async'), 1000)}//bebal transform 后'use strict';var fn = function () {  var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {    return regeneratorRuntime.wrap(function _callee$(_context) {      while (1) {        switch (_context.prev = _context.next) {          case 0:            setTimeout(function () {              return console.log('hello async');            }, 1000);          case 1:          case 'end':            return _context.stop();        }      }    }, _callee, this);  }));  return function fn() {    return _ref.apply(this, arguments);  };}();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编译后,会是这么一个样子,心里还是隐隐有些担忧的。

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

转载于:https://www.cnblogs.com/mfoonirlee/p/7192439.html

你可能感兴趣的文章
Android开发之自定义View(二)
查看>>
python爬虫之微打赏(scrapy版)
查看>>
自制操作系统Antz day08——实现内核 (中) 扩展内核
查看>>
poj-1056-IMMEDIATE DECODABILITY(字典)
查看>>
阿里云容器Kubernetes监控(二) - 使用Grafana展现Pod监控数据
查看>>
区块链应用 | 不知道什么时候起,满世界都在谈区块链的事情
查看>>
小程序爆红 专家:对简单APP是巨大打击
查看>>
FarBox--另类有趣的网站服务【转】
查看>>
在非纯色背景上,叠加背景透明的BUTTON和STATIC_TEXT控件
查看>>
Distributed2:Linked Server Login 添加和删除
查看>>
海量数据处理相关面试问题
查看>>
Python-time
查看>>
Java中取两位小数
查看>>
RTX发送消息提醒实现以及注意事项
查看>>
使用 ftrace 调试 Linux 内核【转】
查看>>
唯一聚集索引上的唯一和非唯一非聚集索引
查看>>
Spark新愿景:让深度学习变得更加易于使用——见https://github.com/yahoo/TensorFlowOnSpark...
查看>>
linux磁盘配额
查看>>
NFS文件共享服务器的搭建
查看>>
%r 和 %s 该用哪个?
查看>>