js异步编程:回调、发布订阅、Promise、async、generator

总结

目前异步编程最好的解决方案: async、await

  • 自带执行器
  • 链式调用
  • 像写同步代码一样处理异步任务
  • await虽好但是不能贪杯,两个任务没有依赖时,用await会增加的执行时间
  • 注意错误处理,会提前终止async函数里面接下的任务

异步

处理命令时,不必等待结果返回即可执行其他命令。当命令返回时再执行响应操作。

|                         |
getData ------------->  doing
|                         |
do a()                    |
do b()                    |
|                         |
cb()    <-------------  done
|                         |

如图,程序执行getData方法获取数据,数据没有马上返回,继续执行a和b,数据返回后再执行getData的回调方法。getData方法称为异步方法。

回调函数

回调函数在工作中经常用到,例子如下:

function getData(url, cb) {
    // 请求url的数据 doing    
    // ...
    // 请求url的数据 done
    // 请求结束后执行cb方法  cb称为回调函数
    cb()
}

// 使用
getData('xxxxxxx.json', function() {
  // doing something
});

回调函数虽然用着简单却极易产生回调地狱,例子如下:

ajax('xxxxxxx.json', function() {
  // doing something 1
  ajax('xxxxxxx.json', function() {
    // doing something 2
    ajax('xxxxxxx.json', function() {
      // doing something 3
      ajax('xxxxxxx.json', function() {
        // doing something 4
      });
    });
  });
});

事件监听 or 发布订阅

按钮上绑定click事件后,按钮被点击则触发响应的事件。这就事件监听。
一般事件监听会是一个单独的类,使用的时候去继承这个类就可以了。

例子如下:

// getData继承事件机制
class getData extends Event {
  getDataFn() {
    // doing something
    // getData触发get事件
    this.trigger('get');
  }
}

let getDataInstance = new getData();

// getData绑定get事件,事件被触发则执行cb方法
getDataInstance.on('get', cb);
// 开始获取数据,获取成功后会触发get事件,然后执行cb方法
getDataInstance.getDataFn();

但实际项目中,某些场景下发布-订阅更常用,其实跟事件绑定是类似的,区别是将时间的绑定、触发单独放在一个主体上。
例子如下:

// 主体
let subject = new Subjet();
// 像向体订阅getData通知
subject.subscribe('getData', function(data) {
  // do something...
});

// 获取数据方法
function getDate(params) {
  // do something...
  //   获取数据后主题发布getData通知
  subject.publish('getData');
}

Promise

用Promise可以包装出异步任务,她会返回一个Promise对象,Promise的.then来解决异步任务的回调,并且支持链试调用。
Promise详细文档请阅读:阮一峰老师的ES6入门:Promise
例子如下:

var getData = new Promise((resolve, reject) => {
  // do something... 比如请求http获取数据
  // 成功了
  resolve('成功了');
});

getData
  .then(
    sucRes => {
      // 成功回调
      // Promise对象支持链式调用,在`.then`方法从return出的参数会被包装成Promise,供下一个。then方法使用
      // 详细的api说明百度一下有很多
      return '链式调用';
    },
    err => {
      // 失败回调
    }
  )
  .then(
    sucRes => {
      // 成功回调
    },
    err => {
      // 失败回调
    }
  );

Promise.all() 也是经常使用的方法,用于将多个 Promise 实例,包装成一个新的 Promise 实例。
我们直接看例子:

// 现在假设有P1,P2,P3 三个Promise实例,我们需要这三个实例都完成后,再执行一些操作。
// 这时就可以用Promise.all()
Promise.all([P1, P2, P3]).then(
  sucRes => {
    // P1,P2,P3都成功时,执行回调
  },
  err => {
    // P1,P2,P3有任意一个Promise失败时,执行回调
  }
);

async、await

asyncGenerator函数的语法糖,不知道Generator的可以查看任一峰老师的ES6:Generator。
async 返回的值,会被包装成Promise对象
例子如下:

// async 返回的值,会被包装成Promise对象,可以提供链式调用
async function getData(params) {
  return '完成';
}
getData.then(suc => {});

await 可以让异步代码看起来像同步代码,就像他的文字含义一样。await:等待
例子如下:

// 异步任务P1 3秒后完成
function P1(params) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('完成');
    }, 3000);
  });
}
// async 内部试用await控制异步任务
async function getData(params) {
  // getData方法执行后,会等待异步任务P1执行完成(3秒后),再返回成功了
  //   这里的异步处理,跟同步的代码是很像的
  // .catch 处理异常,async方法返回的是一个Promise对象
  //   当遇到没有处理的Error时候,会执行reject直接返回,跳过接下来的任务
  await P1().catch(err => {});
  // P1完成后,do something other
  return '完成了';
}

Generator、执行器

上面提到的async可以简单理解为Generator + 执行器。详情:阮一峰老师的ES6入门:Generator
Generator函数在我理解,其实是异步编程方法的一个中间产物,能用但是不够好用。
Generator可以控制函数按步骤执行,例子如下:

// `*`可以声明一个Generator函数,yield定义一个停顿点(返回值)
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

Generator函数的执行,需要手动控制,编程的时候代码相当繁琐。因此需要搭配一个执行器,比如Thunk函数。
如果你想深入了解 Generator、执行器、Async函数之间的关系,推荐以下文章:
Generator 函数的含义与用法
执行器:Thunk 函数的含义和用法
co 函数库的含义和用法
async 函数的含义和用法

你可能感兴趣的:(promise,异步,async,js,学习笔记)