同步与异步

  • 通常,代码是由上而下依次执行的。如果有多个任务,就必须排队,前一个任务完成,后一个任务才能执行。这种连续的执行模式就叫做同步。
a();
b();
c();
复制代码

上面代码中,a、b、c是三个不同的函数,每个函数都是一个不相关的任务。在同步模式会先执行 a 任务,再执行 b 任务,最后执行 c 任务。当b任务是一个耗时很长的请求时,而c任务是展现新页面时,就会导致网页卡顿。

  • 所谓异步,就是一个任务不是连续完成的。比如,有一个读取文件处理的任务,任务的第一段的向操作系统发出请求,要求读取文件,然后程序执行其他任务,等到操作系统返回文件,再去处理文件。这种不连续的执行模式就叫做异步。
a();
//立即发送请求
ajax('url',(b)=>{//请求回来执行
});
c();
复制代码

上面代码中,就是将b任务分成了两部分。一部分立即执行,另一部分再请求回来后执行。也就解决了上面的问题。

总结: 同步就是大家排队工作,异步就是大家同时工作。

异步的解决方案

1、CallBack

CallBack,即回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。即异步操作执行完成后触发执行的函数。

//当请求完成时就会触发回调函数
$.get('url',callback);
复制代码

回调可以完成异步操作,但用过 jquery 的 PE 都对下面的代码折磨过。

$.ajax({url:"type",data:1,success:function(a){url:"list",data:a,success:function(b){$.ajax({url:"content",data:b,success:function(c){console.log(c);}})}}
})
复制代码

上面代码就是传说中的回调金字塔,又叫回调地狱。这里还只是单纯的嵌套代码,如若再加上业务代码,代码可读性可想而知。自己开发起来还好,如果这是别人的代码,你要改其中一部分足以让人疯掉。

2、事件发布订阅

我们想读取两个文件时,希望这两个文件都被读取完后,拿到结果。我们可以通过 node 中的 EventEmitter 类来实现,它有两个核心方法,一个是 on(表示注册监听事件),一个是emit(表示发射事件)。

let fs=require('fs');
let EventEmitter=require('event');
let eve=new EventEmitter();
let arr=[];//存储读取内容
//监听数据获取成功事件,然后调用回调函数
eve.on('ready',function(d){arr.push(d);if(arr.length==2){//两个文件的数据console.log(arr);}
});
fs.readFile('./a.txt','utf8',function(err,data){eve.emit('ready',data);
});
fs.readFile('./b.txt','utf8',function(err,data){eve.emit('ready',data);
});
复制代码

请求 a.txt 和 b.txt 文件数据,当成功后发布ready事件。on 订阅了 ready 事件,当监听到触发的时候,on 方法执行。

哨兵变量

let fs=require('fs');
function after(times,callback){let arr=[];return function(d){arr.push(d);if(arr.length==times){callback(arr);}}
}
//2是一个哨兵变量,将读取文件数据成功后执行的方法作为回调函数传给after方法
let out=after(2,function(data){console.log(data);
})
fs.readFile('./a.txt','utf8',function(err,data){out(data);
});
fs.readFile('./b.txt','utf8',function(err,data){out(data);
});
复制代码

上面代码 after 方法执行时传入的 2 相当于一个哨兵变量,需要读取几个文件的数据就传几。将需要读取的文件数量,和读取全部文件成功后的方法作为回调函数传入 after。out 为 after 执行后返回的函数,每次获取文件成功后执行 out 方法可以后去到最终全部文件的数据。

不使用回调地狱遇到的问题是:不知道读取文件的函数什么时候执行完。只有当全部读取完成后才能执行需要文件数据的方法。

3、Generator函数

Generator 函数要用*号来标识,yield 关键字表示暂停执行的标记。Generator 函数是一个状态机,封装了多个内部状态。调用一次 next 就会继续向下执行,返回的结果是一个迭代器,所以 Generator 函数还是一个遍历器对象生成函数。

function* read(){let a=yield '123';console.log(a);let b=yield 4;console.log(b);
}
let it = read();
console.log(it.next('321')); // {value:'123',done:false}
console.log(it.next(1)); // 1 {value:4,done:false}
console.log(it.next(2)); // 2 {value:2,done:true}
console.log(it.next(3)); // {value:undefined,done:true}
复制代码

上面代码可以看出,调用 Generator 函数后,返回的不是函数运行的结果,而是一个指向内部状态的指针对象,也就是遍历器对象。必须调用遍历器对象的 next 方法,让指针移动的下一个状态。内部指针就会从函数开始或上次定下来的地方开始执行,直到遇到下一个 yield 语句或 return 语句为止。value 属性表示当前的内部状态值,是 yield 语句后面那个表达值;done 属性是一个布尔值,表示是否遍历结束。

4、Promise

在 JavaScript 的异步发展史中,出现了一系列解决 callback 弊端的库,而 promise 成为了胜者,并成功地被加入了ES6标准。Promise 函数接受一个函数作为参数,该函数有两个参数 resolve 和 reject。promise 就像一个中介,而它只返回可信的信息给 callback,所以 callback 一定会被异步调用,且只会被调用一次。

let p=new Promise((resolve,reject)=>{//to do...if(/*异步操作成功*/){resolve(value);}else{reject(err);}
});p.then((value)=>{//success
},(err)=>{//failure
})复制代码

这样 Promise 就解决了回调地狱的问题,比如我们连续读取多个文件时,写法如下:

let fs=require('fs');
function read(url){return new Promise((resolve,reject)=>{fs.readFile(url,'utf8',function(err,data){if(err) reject(err);resolve(data);})}
}read('a.txt').then((data)=>{console.log(data);
}).then(()=>{return read('b.txt');
}).then((data)=>{console.log(data);
}).catch((err)=>{console.log(err);
})
复制代码

如此不断的返回一个新的 Promise,这种不断的链式调用,就摆脱了callback回调地狱的问题和异步代码非线性执行的问题。

Promise 还解决了 callback 只能捕获当前错误异常。Promise 和 callback 不同,Promise 代理着所有的 callback 的报错,可以由 Promise 统一处理。所以,可以通过catch来捕获之前未捕获的异常。

Promise解决了callback的回调地狱问题,但Promise并没有摆脱callback。所以,有没有更好的写法呢?

5、Async Await

async函数是ES7中的一个新特性,它结合了Promise,让我们摆脱callback的束缚,直接用类同步的线性方式写异步函数,使得异步操作变得更加方便。

let fs=require('fs');
function read(url){return new Promise((resolve,reject)=>{fs.readFile(url,'utf8',function(err,data){if(err) reject(err);resolve(data);})}
}async function r(){let a=await read('a.txt');let b=await read('b.txt');return a+b;
}
r().then((data)=>{console.log(data);
});复制代码

至此,异步的 await 函数已经可以让我们满意。目前使用 Babel 已经支持 ES7 异步函数的转码了,大家可以在自己的项目中开始尝试。以后会不会出现更优秀的方案?以我们广大程序群体的创造力,相信一定会有的。

JavaScript异步调用的发展历程就到这里了,如果您觉得文章有用,可以打赏个咖啡钱(⊙﹏⊙)!

原文发布时间为:2018年06月27日

原文作者:afan

本文来源:掘金 如需转载请联系原作者

JavaScript异步调用的发展历程相关推荐

  1. 马蹄疾 | 详解 JavaScript 异步机制及发展历程(万字长文)

    本文从Event Loop.Promise.Generator.async await入手,系统的回顾 JavaScript 的异步机制及发展历程. 需要提醒的是,文本没有讨论 nodejs 的异步机 ...

  2. 短小强悍的JavaScript异步调用库

    原文链接:  7 lines JavaScript library for calling asynchronous functions  翻译人员: 铁锚 翻译时间: 2014年02月18日 示例地 ...

  3. 细说JavaScript异步函数发展历程

    2019独角兽企业重金招聘Python工程师标准>>> < The Evolution of Asynchronous JavaScript >外文梳理了JavaScri ...

  4. 一篇需要膜拜的文篇--Javascript异步编程模型进化(转)

    要我能用得这么熟, 那前端出师了哈. http://foio.github.io/javascript-asyn-pattern/ 改天一个一个亲测一下. Javascript语言是单线程的,没有复杂 ...

  5. 切面是异步还是同步操作‘_细说JS异步发展历程

    知其然知其所以然,首先了解三个概念: 1.什么是同步? 所谓同步,就是在发出一个"调用"时,在没有得到结果之前,该"调用"就不返回.但是一旦调用返回,就得到返回 ...

  6. javascript模块化发展历程

    什么是模块化 ? 为什么要做Javascript模块化? JavaScript 模块化发展历程 什么是模块化 ? 模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职 ...

  7. JAVASCRIPT发展历程

    JAVASCRIPT发展历程 诞生 JavaScript 因为互联网而生,紧跟着浏览器的出现而问世.回顾它的历史,就要从浏览器的历史讲起. 1990年底,欧洲核能研究组织(CERN)科学家 Tim B ...

  8. JavaScript模块化开发的演进历程

    写在前面的话 js模块化历程记录了js模块化思想的诞生与变迁 历史不是过去,历史正在上演,一切终究都会成为历史 拥抱变化,面向未来 延伸阅读 - JavaScript诞生(这也解释了JS为何一开始没有 ...

  9. 如何从异步调用返回响应?

    我有一个函数foo ,它发出Ajax请求. 如何返回foo的响应? 我尝试从success回调中返回值,以及将响应分配给函数内部的局部变量并返回该局部变量,但这些方法均未真正返回响应. functio ...

最新文章

  1. centos7 lvm管理 把/home空间转移给/
  2. html语言的空格键,如何在如何在HTML中插入空格中插入空格
  3. 机器学习知识点(六)增广矩阵求解拉格朗日乘子法的Java实现
  4. APM - 零侵入监控Http服务
  5. 201671010406 丁家辉《英文文本统计分析》结对项目报告
  6. 关于局部变量表slot的理解
  7. SparkContext解析
  8. Quickr for Portal相关网址
  9. 微信小程序UI框架之【weui】怎样使用
  10. Ant Design 遭删库!
  11. Scala历史版本在哪里??
  12. 终端数据防泄漏案例分析
  13. 写博客没高质量配图?python爬虫教你绕过限制一键搜索下载图虫创意图片!
  14. Java 解析复杂表格excel
  15. 如何将交叉引用参考文献批量变为上标
  16. [题解][Codeforces 1139A~1139F]Codeforces Round #548 (Div. 2) 简要题解
  17. python降低图片分辨率_手把手:扫描图片又大又不清晰?这个Python小程序帮你搞定!...
  18. mysql统计用户留存_SQL 统计用户留存
  19. 地铁照明中的智能照明控制系统
  20. 管理 Oracle Cluster Registry(OCR)

热门文章

  1. java 线程的创建和执行_线程管理(一)线程的创建和运行
  2. C语言程序设计第十章字符串,C语言程序设计(字符串)
  3. 我看过的数据库方面的好文章
  4. java volatile 多线程,java多线程-volatile的使用
  5. @ImportResource SpringBoot使用xml配置Bean
  6. Spring Cloud Alibaba 消息队列:基于 RocketMQ 实现服务异步通信
  7. 使用Ef框架进行分页..EF模糊查询..EF多表内连接查询
  8. python环境安装opencv,Python环境搭建之OpenCV的步骤方法
  9. 自旋电子学与量子计算机,基于“分子自旋电子学”的新技术,将给量子计算机带来新希望!...
  10. sketch如何做设计稿交互_交互设计师是做什么的——交互设计的历史、现状和未来...