异步控制流模式

create on in javascript with 0 comment and 221 view

Node.js是一个将异步API作为规范的平台,在Node.js应用程序中,异步代码语句的执行顺序难以预测,它让我们遇到像迭代一组文件,顺序执行操作或等待一组操作完成这些简单的问题时变得艰难.开发人员需要采用额外的方式去处理这些问题,这样会使得代码效率低,可读性差,最常见的错误则是陷入回调地狱,通过不停地嵌套和代码的水平增长,使得即便简单的程序也难以阅读和理解.
当我们能预先知道代码可能变得笨拙时,则需要去采取适当的解决方案,这是很重要的.

我们来看一下这段简单的爬虫代码:

const request = require('request'); const fs = require('fs'); const mkdirp = require('mkdirp'); const path = require('path'); const utilities = require('./utilities'); function spider(url, callback) { const filename = utilities.urlToFilename(url); fs.exists(filename, exists => { // 通过验证是否尚未创建相应的文件来检查URL是否已下载 if(!exists) { console.log(`Downloading ${url}`); request(url, (err, response, body) => { //如果找不到文件,则使用以下代码行下载URL if(err) { callback(err); } else { mkdirp(path.dirname(filename), err => { //然后确定包含文件的目录是否存在 if(err) { callback(err); } else { fs.writeFile(filename, body, err => { //最后将HTTP响应的正文写入文件系统 if(err) { callback(err); } else { callback(null, filename, true); } }); } }); } }); } else { callback(null, filename, false); } }); } // 调用spider函数,并提供url作为参数 spider(process.argv[2], (err, filename, downloaded) => { if(err) { console.log(err); } else if(downloaded){ console.log(`Completed the download of "${filename}"`); } else { console.log(`"${filename}" was already downloaded`); } });

上面的代码实现的功能非常简单,就是去爬取对应网站页面并保存在本地.
但是从代码的结构可以看出,代码出现了几个问题.

  1. 多个几个级别的缩进,嵌套太深,代码难以阅读.
  2. 难以跟踪某个功能在哪里结束,在哪里开始.
  3. 每个作用域使用的变量名重叠,例如err.
  4. 大量的闭包可能造成不容易识别的内存泄露.
    这些问题都是回调地狱的主要情况,下面提出我们解决的方案.

使用纯javascript

修复回调地狱其实可以不需要任何库和花式的技巧,只需要我们遵循回调规则,就能保持低嵌套等级,改善代码结构.

  • 必须尽快退出
    根据上下文,使用return,break或continue,立即退出当前语句,而不是编写和嵌套 if…else语句.

  • 为回调创建命名函数,将他们保持在闭包之外,并将中间结果作为参数传递.

  • 对代码进行模块化

第一步优化:删除else语句以重构错误检查模式

if(err) { callback(err) } else { // code... }

改为

if(err) { return callback(err); } // code

第二步优化:尝试构建可重用代码段
我们可将两个功能提取出来

  1. 将给定字符串写入文件的功能
function saveFile(filename, contents, callback) { mkdirp(path.dirname(filename), err => { if(err) { return callback(err); } fs.writeFile(filename, contents, callback); }); }
  1. 将URL和文件名作为输入,并将url下载到给定的文件中
function download(url, filename, callback) { console.log(`Downloading ${url}`); request(url, (err, response, body) => { if(err) { return callback(err); } saveFile(filename, body, err => { if(err) { return callback(err); } console.log(`Downloaded and saved: ${url}`); callback(null, body); }); }); }

最后,我们的spider函数简化为

function spider(url, callback) { const filename = utilities.urlToFilename(url); fs.exists(filename, exists => { if(exists) { return callback(null, filename, false); } download(url, filename, err => { if(err) { return callback(err); } callback(null, filename, true); }) }); }

通过一些规则,我们能够大大减少代码的嵌套层级,同时增加可重用性和可测试性.实际上,我们可以考虑导出saveFile()和download(),以便于其它模块使用.
在平时编码中,我们应多运用这些规则,来确保了不滥用闭包和匿名函数,这样做效果显著,且不费力.

😁😂😃😄😅😆😇😈😉😐😑😒😓😔😕😖😗😘😙😠😡😢😣😤😥😦😧😨😩😰😱😲😳😴😵😶😷😸😹🙀🙁🙂🙃🙄🙅🙆🙇🙈
🙂