异步发展之进化历程

 秋天麦子

昨天是中秋节,前段之间一直在研究这个异步操作,从之前的文章中也可以看出来,但这段时间公司比较忙,基本每天加班,所以拖到今天才更新自己的博客,记录之前学习的东西。仅供参考,如有版权问题,请联系我。

  1. 旧时代之事件回调

  • 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
复制代码
  1. 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('失败了');
})
复制代码
  1. 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 );
});

复制代码
  1. 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
喜欢就支持一下吧
点赞0 分享