深切探析koa之异步回调处置惩罚篇

在上一篇中我们梳理了koa当中中间件的洋葱模子实行道理,并完成了一个可以让洋葱模子自动跑起来的流程治理函数。这一篇,我们再来研究一下koa当中异步回调同步化写法的道理,一样的,我们也会完成一个治理函数,是的我们可以经由历程同步化的写法来写异步回调函数。

1. 回调金字塔及抱负中的解决方案

我们都晓得javascript是一门单线程异步非壅塞言语。异步非壅塞当然是它的一个长处,但大批的异步操纵必定触及大批的回调函数,特别是当异步嵌套的时刻,就会涌现回调金字塔的题目,使得代码的可读性异常差。比方下面一个例子:

var fs = require('fs');

fs.readFile('./file1', function(err, data) {
  console.log(data.toString());
  fs.readFile('./file2', function(err, data) {
    console.log(data.toString());
  })
})

这个例子是前后读取两个文件内容并打印,个中file2的读取必需在file1读取终了以后再举行,因而其操纵必须要在file1读取的回调函数中实行。这是一个典范的回调嵌套,而且只要两层罢了,在现实编程中,我们可能会碰到更多层的嵌套,如许的代码写法无疑是不够文雅的。

在我们想象中,比较文雅的一种写法应该是看似同步实则异步的写法,相似下面如许:

var data;
data = readFile('./file1');
//下面的代码是第一个readFile实行终了以后的回调部份
console.log(data.toString());
//下面的代码是第二个readFile的回调
data = readFile('./file2');
console.log(data.toString());

如许的写法,就完全避免回调地狱。事实上,koa就让我们可以运用如许的写法来写异步回调函数:

var koa = require('koa');
var app = koa();
var request=require('some module');

app.use(function*() {
  var data = yield request('http://www.baidu.com');
  //以下是异步回调部份
  this.body = data.toString();
})

app.listen(3000);

那末,终究是什么让koa有这么奇异的魔力呢?

2. generator合营promise完成异步回调同步写法

症结的一点,实在前一篇也提到了,就是generator具有相似”打断点”如许的结果。当碰到yield的时刻,就会停息,将掌握权交给yield背面的函数,当下次返回的时刻,再继承实行。

而在上面的谁人koa例子中,yield背面的可不是任何对象都可以哦!必需是特定范例。在co函数中,可以支撑promise, thunk函数等。

本日的文章中,我们就以promise为例来举行剖析,看看怎样运用generator和promise合营,完成异步同步化。

照旧以第一个读取文件例子来剖析。起首,我们须要将读文件的函数举行革新,将其封装成为一个promise对象:

var fs = require('fs');

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

//下面是readFile运用的示例
var tmp = readFile('./file1');
tmp.then(function(data) {
  console.log(data.toString());
})

关于promise的运用,假如不熟习的可以去看看es6中的语法。(近期我也会写一篇文章来教人人怎样用es5的语法来本身完成一个具有基本功用的promise对象,敬请期待呦^_^)

简朴来说,promise可以完成将回调函数经由历程 promise.then(callback)的情势来写。然则我们的目的是合营generator,真正完成如丝般顺滑的同步化写法,怎样合营呢,看这段代码:

var fs = require('fs');

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

//将读文件的历程放在generator中
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}

//手动实行generator
var g = gen();
var another = g.next();
//another.value就是返回的promise对象
another.value.then(function(data) {
  //再次挪用g.next从断点处实行generator,并将data作为参数传回
  var another2 = g.next(data);
  another2.value.then(function(data) {
    g.next(data);
  })
})

上述代码中,我们在generator中yield了readFile,回调语句代码写在yield以后的代码中,完全是同步的写法,完成了文章一开首的想象。

而yield以后,我们获得的是一个another.value是一个promise对象,我们可以运用then语句定义回调函数,函数的内容呢,则是将读取到的data返回给generator并继承让generator从断点处实行。

基本上这就是异步回调同步化最中心的道理,事实上假如人人熟习python,会晓得python中有”协程”的观点,基本上也是运用generator来完成的(我想当疑心es6的generator就是自创了python~)

不过呢,上述代码我们依然是手动实行的。那末同上一篇一样,我们还须要完成一个run函数,用于治理generator的流程,让它可以自动跑起来!

3. 让同步化回调函数自动跑起来:一个run函数的编写

仔细观察上一段代码中手动实行generator的部份,也能发明一个规律,这个规律让我们可以直接写一个递归的函数来替代:

var run=function(gen){
  var g;
  if(typeof gen.next==='function'){
    g=gen;
  }else{
    g=gen();
  }

  function next(data){
    var tmp=g.next(data);
    if(tmp.done){
      return ;
    }else{
      tmp.value.then(next);
    }
  }

  next();
}

函数吸收一个generator,并让个中的异步可以自动实行。运用这个run函数,我们来让上一个异步代码自动实行:

var fs = require('fs');

var run = function(gen) {
  var g;
  if (typeof gen.next === 'function') {
    g = gen;
  } else {
    g = gen();
  }

  function next(data) {
    var tmp = g.next(data);
    if (tmp.done) {
      return;
    } else {
      tmp.value.then(next);
    }
  }

  next();
}

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

//将读文件的历程放在generator中
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}
//下面只须要将gen放入run当中即可自动实行
run(gen);

实行上述代码,即可看到终端顺次打印出了file1和file2的内容。

须要指出的是,这里的run函数为了简朴起见只支撑promise,而现实的co函数还支撑thunk等。

如许一来,co函数的两大功用基本就完全引见了,一个是洋葱模子的流程掌握,另一个是异步同步化代码的自动实行。鄙人一篇文章中,我将带人人对这两个功用举行整合,写出我们本身的一个co函数!

这篇文章的代码一样可以在github上面找到:https://github.com/mly-zju/async-js-demo,个中promise_generator.js就是本篇的示例源码。

一样迎接人人多多关注我的github pages个人博客哦,会不定期更新我的技术文章~

    原文作者:勇敢的半导体
    原文地址: https://segmentfault.com/a/1190000007531525
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞