异步编程(promise/generator/async-await)

1. 传统异步编程

在传统异步编程中,通常采用回调函数的方式完成异步操作,比如setTimeout、setInterval、requestAnimationFrame等api,或者以下例子:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

2. promise

定义

​ 可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolvereject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。

then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。

不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:

  • 在 本轮 Javascript event loop(事件循环)运行完成 之前,then回调函数是不会被调用的。

  • 通过 then() 添加的回调函数总会被调用(状态不为pending时调用),即便它是在异步操作完成之后才被添加的函数。

  • 通过多次调用 then(),可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行。

因此,Promise 最直接的好处就是链式调用(chaining),避免了回调地狱,异常捕获也可以“冒泡”

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

改写传统异步编程例子如下:

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

Api

  1. [Promise.all()](http://es6.ruanyifeng.com/#docs/promise#Promise.all()) 全为fulfilled或者出现第一个rejected后触发then回调。

  2. [Promise.race()](http://es6.ruanyifeng.com/#docs/promise#Promise.race()) 只要其中有一个实例率先改变状态,整个的状态就跟着改变。

兼容性

promise

总结

​ 可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

​ Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

3. generator

定义

Generator 函数是一个状态机,封装了多个内部状态,通过执行 Generator 函数会返回一个遍历器对象。

Api

  • yield表达式:

    ​ 暂停执行的标记;

    ​ 通过yield* 可以嵌套Generator函数;

  • next():

    ​ 只有调用next(),内部指针指向该语句时才会执行,等于手动“惰性求值“;

    ​ next方法调用时传参,该参数就会被当作上一个yield表达式的返回值,可以在 Generator 函数开始运行之后,继续向函数体内部注入值;

    ​ for...of可以自动遍历Generator 函数返回的遍历器对象,替代next();

  • return():

    ​ 返回给定的值并结束生成器;

    ​ 同样可以通过传参指定返回结果;

function* helloWorldGenerator() {//给function后添加*,声明一个Generator 函数
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()//next方法可以恢复执行
// { value: 'hello', done: false }

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

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

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

异步应用

虽然表示简洁了,但如果必须保证前一步执行完,才能执行后一步。光靠generator函数在流程管理上是不方便的(不太方便找到合适的时机执行的)。

​ 通过以下两种方案可以做到:

(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。(Thunk模块)

(2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。 (co模块)

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) return reject(error);
      resolve(data);
    });
  });
};

var gen = function* (){
  var f1 = yield readFile('/etc/fstab');
  var f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
var g = gen();

g.next().value.then(function(data){
  g.next(data).value.then(function(data){
    g.next(data);
  });
});

总结

相对于promise的代码冗余问题,generator函数的使用更为简洁。但是Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。

4. async-await

含义

​ async 函数就是 Generator 函数的语法糖。

Api

​ async:在function 前面带上async,用于声明async函数。

​ await:该指令会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果。如果不是 Promise 对象,就直接返回对应的值。

改进

async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。

(2)更好的语义。

(3)更广的适用性。

(4)返回值是 Promise。

兼容性

async

总结

​ async是揉合了promise和generator各自的优势,提炼而出的异步编程方式。

对比

三者对比

sync 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。

补充

promise.resolve() 和 promise.reject() 的应用场景

创建promise对象的语法糖,用于将指定值或对象转化为promise对象。 Promise.resolve方法有下面三种形式:

Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(theanable);

这三种形式都会产生一个新的Promise。其中:

第一种形式提供了自定义Promise的值的能力,它与Promise.reject(reason)对应。两者的不同,在于得到的Promise的状态不同。

第二种形式,提供了创建一个Promise的副本的能力。

第三种形式,是将一个类似Promise的对象转换成一个真正的Promise对象。它的一个重要作用是将一个其他实现的Promise对象封装成一个当前实现的Promise对象。例如你正在用bluebird,但是现在有一个Q的Promise,那么你可以通过此方法把Q的Promise变成一个bluebird的Promise。

实际上第二种形式可以归在第三种形式中。

generator的应用场景

  1. 可以生成一个无限列表,每次获取都递增1 (创建无限长的序列)

  2. 产生一个每次获取一个元素都有重要、高消耗的资源访问的列表,完成延迟加载模型来将高消耗的元素获取延迟到真正访问时,而不需要一开始就获取N次形成静态的数组(延迟执行)

  3. 管理异步流程

  4. 可以模拟长轮询实现

generator try...catch

Generator 函数内部有try...catch则在内部捕获内部的错误,若内部没有部署try...catch代码块,内部抛出的错误直接被外部catch代码块捕获。

如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。

Last updated

Was this helpful?