最近学习es6 看到promise和generater,最后发现他们的共同点都是通过维护状态来解决异步的一种方式,
由于js单线程运行的特点,所以前端编程一直离不开异步这个概念,先把这个知识点进行梳理。
先看同步问题,同步很简单就是程序排队执行,但同步会存在阻塞问题
同步阻塞:
// 这是一个阻塞式函数, 将一个文件复制到另一个文件上
function copyBigFile(afile, bfile){
var result = copyFileSync(afile,bfile);
return result;
}
如上所示若copyFileSync用时一小时那么一小时后copyBigFile才会有返回结果,这就是同步阻塞。
所以js异步的出现来解决同步阻塞问题
异步概念:异步就是cpu跳过等待,先处理后续的任务(JavaScript 语言对异步编程的实现,就是回调函数)
先看js在单线程下如何实现异步操作:
如上图为JS运行机制图(事件循环示例图),流程如下:
step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;
step4: 主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);
step5: 重复执行step2、3、4;称为事件循环。
执行的大意:
同步环境执行(step1) -> 事件循环1(step4) -> 事件循环2(step4的重复)…
其中的异步进程有:
a、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
b、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
c、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。
知道原理后再看常见js中异步处理方式:
除开上面提到的 事件、setTimeout、Ajax请求较熟悉的外这里介绍而是es6 的几种异步处理方式:
1.Promise
回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A
文件之后,再读取B
文件,代码如下。
fs.readFile(fileA, 'utf-8', function (err, data) {
fs.readFile(fileB, 'utf-8', function (err, data) {
// ...
});
});
不难想象,如果依次读取两个以上的文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。因为多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为"回调函数地狱"(callback hell)。
Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用 Promise,连续读取多个文件,写法如下。
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function (data) {
console.log(data.toString());
})
.then(function () {
return readFile(fileB);
})
.then(function (data) {
console.log(data.toString());
})
.catch(function (err) {
console.log(err);
});
上面代码中,我使用了fs-readfile-promise
模块,它的作用就是返回一个 Promise 版本的readFile
函数。Promise 提供then
方法加载回调函数,catch
方法捕捉执行过程中抛出的错误。
可以看到,Promise 的写法只是回调函数的改进,使用then
方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
当然,还有Generator函数,比promise更加优化,后续补充。