昨天是中秋节,前段之间一直在研究这个异步操作,从之前的文章中也可以看出来,但这段时间公司比较忙,基本每天加班,所以拖到今天才更新自己的博客,记录之前学习的东西。仅供参考,如有版权问题,请联系我。
-
旧时代之事件回调
- readFile()会在文件 I/O 返回结果之后触发回调函数,通过这种嵌套的方式,我们能够保证文件是按序读取的,这种方式在JS中十分常见,比如定时器setInterval()、setTimeout()。回调实现的异步代码易于理解,但问题也很明显,层层嵌套使得代码逐层缩进,严重降低了可读性,我们把这种现象称为回调金字塔。
const fs = require('fs');
fs.readFile('./package.json',function(err,data){
if(err){
console.log(err);
}else{
data =JSON.parse(data);
console.log(data.name);
fs.readFile('./package1.json',function(err,data1){
if(err){
console.log(err);
}else{
data1 = JSON.parse(data1);
console.log(data1.name);
fs.readFile('./package2.json',function(err,data2){
console.log(err);
})else{
data2 = JSON.parse(data2);
console.log(data2.name);
}
}
})
}
})
// node cb-promise.js
复制代码
-
Promise 承诺异步
- ES6出现之前,饱受回调地狱煎熬,这在我学习nodeJs中express框架中已经见识到了,使用ES6中的Promise对象实现异步,就彻底告别了“回调地狱”,Promise本质依然是事件回调,是基于事件机制实现。
function readFileAsync(path) {
return new Promise((resolve,reject)=>{
fs.readFile(path,(err,data)=>{
if(err) reject(err)
else resolve(data)
})
})
}
readFileAsync('./package.json')
.then(data=>{
data = JSON.parse(data);
console.log(data.name);
})
.catch(err=>{
console.log(err);
})
复制代码
- node8.0+版本以上独立封装Promise
const fs = require('fs');
const util = require('util'); //封装了一个返回Promise对象的方法
util.promisify(fs.readFile)('./package.json')
.then(data=>{
data = JSON.parse(data);
console.log(data.name);
})
.catch(err=>{
console.log(err);
})
复制代码
- 高版本Ajax
Promise.all([
$.ajax({url:'data/arr.txt',dataType:'json'}), //高版本ajax返回的是Promise对象
$.ajax({url:'data/json.txt',dataType:'json'})
]).then(function (results) {
let [arr,json] = results;
alert('全部成功了');
alert(arr);
console.log(json);
},function (err) {
alert('失败了');
})
复制代码
-
Generator 构造新方式
-
Generator函数也是ES6标准引入的新的特新,按照阮一峰老师的说法《Generator 函数的语法》,Generator函数是一个状态机,封装了多个内部状态,它提供了一种机制,通过yield关键字和next()方法来交付和归还线程的执行权,实现代码异步。
说到Generator函数 就不得不说Iterator迭代器,手写一个简易的迭代器
function makeIterator(arr) {
let nextIndex = 0;
return{
next:()=>{
if(nextIndex<arr.length){
return { value:arr[nextIndex++] , done:false ,index:nextIndex}
}else{
return{ done: true}
}
}
}
}
const it = makeIterator(['吃饭','睡觉','打豆豆'])
console.log("首先"+it.next().value);
console.log("其次"+it.next().value);
console.log("然后"+it.next().value);
console.log("最后"+it.next().value);//console.log("最后"+it.next().done)
复制代码
- Generator函数执行到yield关键字会自动暂停,报出当前状态,返回一个遍历器(Iterator)对象,完成执行权交付。执行下一步通过调用该遍历器的next()方法,回归现场,驱动 Generator 函数从断点处继续执行,完成执行权归还。
生成器函数顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。
生成器函数长什么样呢?
function *makeIterator(arr) {
for (let i = 0; i < arr.length; i++) {
yield arr[i]
}
}
const gen = makeIterator(['吃饭','睡觉','打豆豆']);
console.dir(gen.next());
console.log("其次:"+gen.next().value);
console.log("然后:"+gen.next().value);
console.log("最后:"+gen.next().done);
复制代码
-
我们不难理解Generator函数了,我们再来看看怎么使用Generator函数解决我们的异步回调呢?
还是我们开头的fs函数,Generator + Promise +自己封装的run 方法
const fs = require('fs');
//封装一个读取文件的函数
const read = function(path){
return new Promise(function(resolve, reject){
fs.readFile(path,function(err, data){
if(err) reject('fail');
resolve(data);
})
});
}
const gen = function* (){
try{
let dataA = yield read('package.json'); // yield 在暂停时刻并没有赋值,dataA 的值是在重新执行时刻由 next 方法的参数传入的
console.log('package is :',JSON.parse(dataA).name );
let dataB = yield read('package1.json');
console.log('package1 is :', JSON.parse(dataB).name);
let dataC = yield read('package2.json');
console.log('package2 is :', JSON.parse(dataC).name);
}catch(err){
console.log(err);
}
};
// 驱动 Generator 执行
function run (generator) {
let it = generator();
function go(result) {
// 判断是否遍历完成,标志位 result.done 为 true 表示遍历完成
if (result.done) return result.value;
// result.value 即为返回的 promise 对象
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(gen);
复制代码
-
co来啦 !!解决Run函数自行封装的不统一
co使用
const co = require('co');
const fs = require('fs');
const util = require('util');
co(function *() {
const res = yield util.promisify(fs.readFile)('./package.json');
data = JSON.parse(res);
console.log(data.name);
})
复制代码
-
co 函数库是著名程序员 TJ 大神 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
对比上边的栗子,下边的是不是更像同步代码
const co = require('co');
const fs = require('fs');
const util = require('util');
const readAsync = util.promisify(fs.readFile);
co(function* (){
let dataA = yield readAsync('package.json');
console.log('package is :',JSON.parse(dataA).name );
let dataB = yield readAsync('package1.json');
console.log('package1 is :',JSON.parse(dataB).name );
let dataC = yield readAsync('package2.json');
console.log('package2 is :',JSON.parse(dataC).name );
});
复制代码
-
sAsync/Await 一统世界
- 永远都有更厉害的方法,只有你做不到,没有javascript想不到,ES7推出Async/Await, 语法本质上只是 Generator 函数的语法糖,像 co 一样,它内置了 Generator 函数的自动执行器,并且支持更简洁更清晰的异步写法
const fs = require('fs');
// 封装成 await 语句期望的 promise 对象 ,或者使用util
const readFile = function(){
let args = arguments;
return new Promise(function(resolve, reject){
fs.readFile(...args, function(err, data){
// await 会吸收 resolve 传入的值作为返回值赋给变量
resolve(data);
})
})
};
const asyncReadFile = async function(){
let dataA = await readFile('package.json');
console.log('package is :',JSON.parse(dataA).name );
let dataB = await readFile('package.json');
console.log('package1 is :',JSON.parse(dataB).name );
let dataC = await readFile('package.json');
console.log('package2 is :',JSON.parse(dataC).name );
};
asyncReadFile();
复制代码
总之记住一点:我们是以同步代码的编写方式来执行异步函数,只是解决方案越来越优雅
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END