资讯专栏INFORMATION COLUMN

深入探析koa之异步回调处理篇

Drinkey / 2767人阅读

摘要:而之后,我们得到的是一个是一个对象,我们可以使用语句定义回调函数,函数的内容呢,则是将读取到的返回给并继续让从断点处执行。

在上一篇中我们梳理了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://www.ucloud.cn/yun/80967.html

相关文章

  • 深入探析koa中间件流程控制

    摘要:到此为止,我们就基本讲清楚了中的中间件洋葱模型是如何自动执行的。 koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁。在使用koa的过程中,其实一直比较好奇koa内部的实现机理。最近终于有空,比较深入的研究了一下koa一些原理,在这里会写一系列文章来记录一下我的学习心得和理解。 在我看来,koa最核心...

    fuchenxuan 评论0 收藏0
  • 【全文】狼叔:如何正确的学习Node.js

    摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...

    Edison 评论0 收藏0
  • 【全文】狼叔:如何正确的学习Node.js

    摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...

    fengxiuping 评论0 收藏0
  • 静态分析安全测试(SAST)优缺点探析

    摘要:许多公司都投资于或之类的静态分析安全测试,解决方案。用静态分析方法确保编程安全一书详细描述了静态分析技术的基本原理。博士将静态分析无法找出的诸多安全问题归为瑕疵,而非程序错误。 静态分析安全测试(SAST)是指不运行被测程序本身,仅通过分析或者检查源程序的语法、结构、过程、接口等来检查程序的正确性,那么采用静分析安全测试的方法有什么优缺点呢,且让小编给你说道说道。 许多公司都投资于 H...

    codeKK 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<