异步调用小结

异步调用的历史

注:本文主要参考阮一峰老师的[es6教程](http://es6.ruanyifeng.com/#docs/async)

我们平常希望前一次异步调用的结果发生以后,再产生调用某些函数。比如我们通过node的fs来读取文件,我们需要读取文件A后读取文件B,再读取文件C等。

有什么方式可以实现呢?通过探索,我们发现了以下各种方式的实现。

回调函数

最开始我们用的是回调函数的方法, 但是多次回调会让函数的阅读性大大下降,也不利于我们的调试。 比如像下面这样:

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);
});

注意resolve只能传一个参数,需要多个参数时,可以通过对象或者数组的形式传入。

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

那么,有没有更好的写法呢?

Generator函数

设想一下,我们需要的功能是等待异步调用的结果,再执行某些函数,如果有一个功能,可以在异步调用这里暂停,待结果出来后再执行下面的内容,是不是就达到我们的要求了?

这就是Generator函数

其语法像下面这样

function* asyncJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

function后的*表示这是一个Generator函数,里面只要遇到yield,就会等待调用结果。后面的代码不执行。

它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样

它返回的是一个指针,需要用next调用,每次都返回一个对象,value指当前yield的结果,done指是否结束了。

function* gen(x) {
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

注意这第一个g.next()的value是3,并不是指y是3,而是yield后面的计算x+2为3

不信我们可以试试打印这个y

function* gen(x) {
  var y = yield x + 2;
  console.log('y=',y)
  return y;
}

var g = gen(1);

第一次执行next,计算yield后面的值,然后暂停,并不会继续计算下去,直到我们再调用next(),此时可以看到返回的是undefined

所以如果我们需要在每次next后提供当前yield的值,需要手动在next里添加。

function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }

这里第二步的g.next(2)表示我们从第一个yield获得的结果y为2,所以return的y才为2

错误捕捉

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next();
g.throw('出错了');
// 出错了

上面代码的最后一行,Generator 函数体外,使用指针对象的throw方法抛出的错误,可以被函数体内的try…catch代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

async函数

async函数是基于Generator函数的语法糖

其基本语法如下:

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

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

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncReadFile(); 上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

其更多的语法细节,还是需要看阮一峰老师完整的教程,在这里就不借花献佛了。

三种形式的比较

下面我们通过一个例子来看看三种形式的比较,我们用setTimout来模拟异步请求,时间到了之后会打出promise和时间

var promise = function(t){
  return new Promise((resolve)=>{
    setTimeout(resolve(t),t)    
    })
    .then((t)=> {console.log('promise',t)})
}

promise

//promise链式调用
function demoPromise(){
  promise(100)
    .then(promise(200))
    .then(promise(300))
    .then(promise(400))
}

demoPromise()

generator

//generator调用
function* demoGenerator(){
  try{
    var t1 = yield promise(500)
    var t2 = yield promise(600)
    }catch(err){
      console.log(err)
    }
}

var g = demoGenerator()
g.next()
g.next()

async

//async调用
async function demoAsync(){
  try {
    var t1 = await promise(700)
    var t2 = await promise(800)
  }catch(err){
    console.log(err)
  }
}

demoAsync()

演示地址

通过比较,我们可以看出,promise虽然很好地解决了回调地狱的问题,但是阅读性也不太理想,而Generator和async则通过暂停执行(yield或await)来实现链式调用,其中,async相对于Generator的优势在于,省去手动指针的执行```(next()调用)```,并且有更好的语义,毕竟Generator的```*,yield```对于某些未接触的人,并不能很直接看出其含义。

以上,谢谢.

原文:大专栏  异步调用小结


猜你喜欢

转载自www.cnblogs.com/petewell/p/11584824.html