【js】callback时代的变更
最近团队开始越来越多的使用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); //0
console.log(myGen.next().value); //1
console.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编译后,会是这么一个样子,心里还是隐隐有些担忧的。
想来再结合笔者最近看到的一些历史中的轶事,也颇是觉得其中微妙之处,当有亲身经历者,方可体会的感触。时代的浪潮都在滚滚向前,但愿迎接我们的是新升的朝阳,而非一个漫长的黑夜。
【js】callback时代的变更的更多相关文章
- js callback 和 js 混淆
function test(a,callback){ a+=100; callback(a) } function abc(a){ a+=100; alert(a); } test(5,abc) js ...
- js callback函数
A callback is a function that is passed as an argument to another function and is executed after its ...
- javascript 自己主动绑定JS callback 的方法函数
自己写的一个javascript 智能绑定callback 而且调用运行的函数.主要用于异步请求的 ajax中: <!DOCTYPE html> <html> <head ...
- callbag js callback 标准-支持轻量级观测以及迭代
callbag 是一个js 的回调标准,方便开发支持观测以及迭代的代码 类似已经有好多的实现了 callbag-basics 比rxjs 以及xstream 还快 wonka 说明 基于标准的开发,对 ...
- js callback回调的一种写法
getLocation.cityname(latitude, longitude, function (data1) { SetCityCallBack(data1); }); 定义方法: var g ...
- js url?callback=xxx xxx的介绍
由于安全的原因,浏览器做了很多方面的工作,由此也就引入了一系列的跨域问题,需要注意的是: 跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了.最好的例子是 CSRF ...
- 为什么返回的数据前面有callback? ashx/json.ashx?的后面加 callback=? 起什么作用 js url?callback=xxx xxx的介绍 ajax 跨域请求时url参数添加callback=?会实现跨域问题
为什么返回的数据前面有callback? 这是一个同学出现的问题,问到了我. 应该是这样的: 但问题是这样的: 我看了所请求的格式和后台要求的也是相同的.而且我也是这种做法,为什么他的就不行呢? ...
- vue—你必须知道的 js数据类型 前端学习 CSS 居中 事件委托和this 让js调试更简单—console AMD && CMD 模式识别课程笔记(一) web攻击 web安全之XSS JSONP && CORS css 定位 react小结
vue—你必须知道的 目录 更多总结 猛戳这里 属性与方法 语法 计算属性 特殊属性 vue 样式绑定 vue事件处理器 表单控件绑定 父子组件通信 过渡效果 vue经验总结 javascript ...
- Ext JS 如何动态加载JavaScript创建窗体
JavaScript不需要编译即可运行,这让JavaScript构建的应用程序可以变得很灵活.我们可以根据需要动态从服务器加载JavaScript脚本来创建和控制UI来与用户交互.下面结合Ext JS ...
随机推荐
- hdu 3657 最小割(牛逼!!!!)总算理解了
<strong></strong> 转载:http://blog.csdn.net/me4546/article/details/6662959 加颜色的太棒了!!! 在网上看 ...
- android 上AES解密是报错javax.crypto.BadPaddingException: pad block corrupted
网上看到两种方法: 1.SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(key), "AES"); private sta ...
- springboot + mybatis 完成图片上传并保存到数据库
添加依赖 <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons- ...
- Spring Boot 2.1.5 正式发布,1.5.x 即将结束使命!
Spring Boot 官网在 2019/03/15 这天发布了 Spring Boot 2.1.5 正式版,栈长表示真跟不上了.. 官宣如下 : https://spring.io/blog/201 ...
- MongoDB小结16 - find【查询条件$in】
$in可以查询一个键的多个值 举例,每个人有爱好,假定为一个,数据太多,咱们用第二个参数来过滤一下 db.user.find({},{"_id":0}) { "hobby ...
- jmeter的Classpath即类或者jar包的搜索路径设置
对于master-slave模式,插件和依赖都需要放到slave上才能生效,并且需要重启slave使插件生效 查看配置文件:apache-jmeter-3.1/bin/jmeter.propertie ...
- win7开启超级管理员账户(Administrator)
win7开启超级管理员账户(Administrator) 不同于XP系统,Windows7系统据说出于安全的考虑,将超级管理员帐户"Administrator"在登陆界面给隐藏了, ...
- Python循环定时服务功能(相似contrab)
Python实现的循环定时服务功能.类似于Linux下的contrab功能.主要通过定时器实现. 注:Python中的threading.timer是基于线程实现的.每次定时事件产生时.回调完响应函数 ...
- 斜率优化专题1——bzoj 1597 [Usaco2008 Mar] 土地购买 题解
转载请注明:http://blog.csdn.net/jiangshibiao/article/details/24387147 [原题] 1597: [Usaco2008 Mar]土地购买 Time ...
- leetcode笔记:Search in Rotated Sorted Array
一.题目描写叙述 二.解题技巧 因为这道题出现了旋转的情况,即比第一个元素小的元素可能出如今数值的后半段或者不出现. 因此.能够考虑採用变种的二分查找,即在比較中间元素与目标之前,先比較第一个元素与目 ...