Node.js 101(2): Promise and async

——原文地址:http://blog.chrisyip.im/nodejs-101-package-promise-and-async

先回想一下 Sagase 的项目结构:

lib/
    cli.js
    sagase.js
Gruntfile.js
package.json

上一篇讲了 package.json,这一篇讲 lib/sagase.js

由于代码比較长,就分开一节节地讲,完整的点开 GitHub 看吧。

‘use strict‘;

通知编译器进入 strict
mode
,基本的作用是让编译器帮你检查一些坑,有经验的 JavaScript 开发人员能够忽略。

var fs = require(‘fs-extra‘),
    async = require(‘async‘),
    _ = require(‘lodash‘),
    Promise = require(‘bluebird‘),
    path = require(‘path‘)

引用全部依赖项。假设是使用 --harmony。建议使用 const 取代 varkeyword,可避免变量被改动。

var mar = ‘March‘
mar = ‘June‘
console.log(mar) // ‘June‘

const july = ‘July‘
july = ‘May‘
console.log(july) // ‘July‘

function readFiles (opts) {} 包括非常多信息,一个一个说。

return new Promise(function (resolve, reject) {}

返回一个 Promise 对象。

因为 Node.js 的特点是异步。一般都须要通过异步来处理:

// get all files in DIR
fs.readdir(DIR, function (err, files) {
  if (err) {
    return errorHandler(err)
  }
  // loop files
  files.forEach(function (file) {
    // get the stat of each files
    fs.stat(file, function (err, stat) {
      // if it‘s file
      if (stat.isFile()) {
        // get content of file
        fs.readFile(file, function (err, buff) {
          // do whatever you want
        })
      }
    })
  })
})

假设须要在一个函数里处理非常多事情,甚至说须要让这个函数的返回结果可在多个文件中使用,仅仅靠回调会非常吃力——不知道哪个文件在什么时候才须要使用它的返回结果。

假设使用 Promise,就会简单非常多:

// in a.js
var Promise = require(‘bluebird‘),
    readFile

module.exports = new Promise(function (resolve, reject) {
  fs.readdir(DIR, function (err, files) {
    err ?

 reject(err) : resolve(files)
  })
})

// in b.js
var readFile = require(‘./a.js‘)

readFile
  .then(function (files) {
    // do something with files
    return NEW_RESULT;
  }, function (err) {
    // handle error here
  })
  .then(function (data) {
    // do something with NEW_RESULT
  }, function (err) {
    // handle error here
  })
// in c.js
var readFile = require(‘./a.js‘)

readFile.then(function (files) {
  // the files still exist and accessable
})

通过 Promise 的封装。就能够让 resolve 的结果跨不同的文件,不须要在一个回调函数里处理全部事情。另外,通过 .then() 的第二个函数处理 reject 的错误结果,避免了多重推断。

注意的是,在这里引入了一个叫 bluebird 的第三方
Promise 库。假设 Node.js 版本号是 >= 0.11.13 的话,是不须要引入第三方库的。

async.each(
  files,
  function (file, cb) {
  },
  function (err) {
    err ? reject(err) : resolve(res)
  }
)

async 是一个改善异步回调流程、把异步处理能力赋予普通数组处理函数的库,比方 async.each 就相当于多线程版的 Array.forEach,只是在实际使用中。不要期待运行顺序是乱序或者正序,关键是第三个參数。

async.each([1, 2, 3], function (item, next) {
  console.log(item)
  next()
}, function (err) {
  console.log(‘all tasks done‘)
})
// output:
//   1
//   2
//   3
//   "all  tasks done"

一般来说,由于 fs.stat 是异步调用的,所以 Array.forEach 遍历完数组之后,非常难保证里面的任务是否所有已完毕,这时候调用Promise.resolve() 就无法保证数据的正确性。而通过 async.each 的第三个參数,就能够得知任务的状态,并保证
Promise 能够得到正确的数据。

path.resolve(opts.folder + ‘/‘ + file).replace(process.cwd(), ‘.‘)

path 是一个用于解决和文件夹有关问题的库。path.resolve 会将 ./dir转变为 /Users/USER/PATH/TO/dir (Mac)
格式的完整文件夹。

process.cwd() 会返回调用这个脚本的进程所在文件夹;另外,另一个__dirname 是指脚本所在文件夹:

如果有例如以下文件:

lib/index.js
index.js
// in lib/index.js
module.exports = function () {
  return __dirname
}
// in index.js
console.log(process.cwd()) // ‘/Users/USER/PATH/‘

console.log(require(‘./lib‘)()) // ‘/Users/USER/PATH/lib/‘

剩下的代码不一一解释,大致做了下面工作:

  1. 先读取指定文件夹的全部文件 (fs.readdir)
  2. 使用 async.each 遍历获取的结果
  3. 推断每一个文件的 stat 是不是文件夹 (fs.stat)
    1. 若是,检查符不符合条件,符合则进入下一轮递归 (readFiles)
    2. 若否,检查符不符合条件,符合则加入到终于结果的数组中 (res.push())
  4. 反复 1-3 直至遍历全然部文件

需注意的是:

  • cb() 在多处出现,用于通知 async.each 这个任务已运行完成
  • 递归中用 Promise readFiles.then() 的第一个參数处理 res 的返回、用readFiles.catch 处理 error、用 readFiles.finally 处理 cb() (因必须通知
    async 任务已完毕,在此处统一处理)
  • Promise.finally 是 bluebird 特有的 API,原生 Promise 需这样实现:readFiles.then().catch().then(),第二个 then 相当于 finally (只是不够直观)
function formatOptions (opts) {}

用于格式化传入的參数,就不多解释了。

须要注意的是,在这里用了一个 opts 对象来包括全部參数并传递给readFiles

因为
JavaScript 的特性,用一个 JSON 对象传递參数会方便非常多,比方说:

function formatOptions (folder, pattern, ignoreCase, nameOnly, exclude, excludeNameOnly, recusive) {}

因为 Node.js 用的 V8 仍未包括函数參数默认值,所以最方便的做法是用
JSON 对象:

var options = _.assign({}, {
  key_1: default_value_1,
  key_2: default_value_2
  key_3: default_value_3
}, opts)

而且 JSON 对象也利于扩展——不管是添加还是删除 key,都不须要更改接口。

最后,定义了一个 Sagase 类,并在外部调用这个类时,创建一个新的Sagase 对象:

function Sagase () {
  Object.defineProperties(
    this,
    {
      find: {
        enumerable: true,
        value: function (opts) {
          return readFiles(formatOptions(opts))
        }
      }
    }
  )
}

module.exports = new Sagase()

Object.defineProperty 和 Object.defineProperties 是
ECMAScript 5 中新增加的特性。通过它们就能够创建非常多好玩的东西,比方传统的jQuery.fn.size()

jQuery.fn.size = function () {
  return Number.MAX_SAFE_INTEGER
}

var body = $(‘body‘)
body.length // 1
body.size() // 9007199254740991

换成 ES 5 的写法:

Object.defineProperties(
  jQuery.fn,
  {
    size: {
      enumerable: true,
      get: function () {
        return this.length
      }
    }
  }
)

var body = $(‘body‘)
body.length // 1
body.size // 1

jQuery.fn.size = function () {
  return Number.MAX_SAFE_INTEGER
}

body.size // 1
body.size() // TypeError: number is not a function

合理利用 const 和 Object.defineProperty 能够避开一些非预期的情况,保证程序健壮性。

从 lib/sagase.js 的代码能够看出。Node.js 的异步特性导致函数是一层套一层 (`fs.readdir -> fs.stat
-> isDirectory()) 。写起来事实上不好看。也不利于理解,比方:

function getJSON () {
  var json
  fs.readFile(‘demo.json‘, function (err, buff) {
    json = JSON.parse(buff.toString())
  })
  return json
}

getJSON() // undefined

当然,要在 Node.js 里使用同步接口也是能够的,如 fs.readdirSync,但:

  • Node.js 同步接口不见得比 Python、Ruby 等语言高效
  • 不是全部接口都有同步版本号,如 child_process.exec

为了发挥 Node.js 的优势。就须要正确利用 Promise、async 来编敲代码。比方说有这种一个场景。浏览器端须要获取购物车里全部商品、赠品的数据,常见的步骤大概是:找商品数据,通过商品 ID 找促销规则得到赠品,计算总价,返回结果。这些步骤能够通过多次请求数据库最后用后端语言拼接;假设是 RESTful API 模式,也能够发起多次请求,最后在浏览器端拼接。

假设在浏览器端和server端增加异步处理呢?

var router = express.Router()

router.get(‘/cart‘, function (req, res, next) {
  async.parallel(
    [
      // get all products data
      function (next) {
        request(‘/api/products‘, OPTIONS)
          .then(function (data) {
            next(null, data)
          })
      },
      // get products gifts
      function (next) {
        async.map(
          PRODUCT_LIST,
          function (p, cb) {
            request(‘/api/product/:id/gifts‘, OPTIONS)
              .then(function (data) {
                cb(null, data)
              })
          },
          function (err, results) {
            next(null, results)
          }
        )
      }
    ],
    function (err, results) {
      RESPONSE_BODY = {
        products: results[0],
        gifts: results[1],
        total: calcTotal(results[0])
      }
      res.send(RESPONSE_BODY)
    }
  )
})
$.ajax(‘/cart‘).then(function () {
  // handle products and gifts here
})

通过异步的处理,浏览器就能够用一次请求完毕多次请求的效果。而且不会破坏 RESTful API 的结构。这对于资源紧张、网络环境多变的移动端来说,是很有利的;而对于电脑端则通过降低请求时间来提高交互响应速度。提高用户体验。

这一篇主要内容是如何利用 Promise、async 等库绕开 Node.js 的回调函数坑。回调函数算是 Node.js 最多人黑的地方,假设不能掌控它。写出来的 Node.js 代码将会相当丑陋、不易维护。

而为了让代码好看一些。ECMAScript 6 里增加了一个新特性——GeneratorKoa 已经開始使用
generator 来构建项目。详细怎么用,下一篇说吧。

长按图片识别图中二维码(或搜索微信公众号FrontEndStory)关注“前端那些事儿”。带你了解最新的前端技术。

时间: 2024-10-10 05:07:13

Node.js 101(2): Promise and async的相关文章

进击Node.js基础(二)promise

一.Promise JS动画settimeout,setinterval,requestAnimationFrame,promise npm install bluebird Javascript的特点是异步,Javascript不能等待,如果你实现某件需要等待的事情,你不能停在那里一直等待结果回来,相反,底线是使用回调callback:你定义一个函数,这个函数只有等到结果可用时才能被调用. 这种回调模型对于好的代码组织是没有问题的,但是也可以通过从原始回调切换到promise解决很多问题,将p

进击Node.js基础(二)

一.一个牛逼闪闪的知识点Promise npm install bluebird 二.牛逼闪闪的Promise只需三点1.Promise是JS针对异步操作场景的解决方案 针对异步的场景,业界有很多解决方案,如回调.事件机制 Promise是一个对象,同时它也一种规范,针对异步操作约定了统一的接口,表示一个异步操作的最终结果以同步的方式来写代码,执行的操作是异步的,但是又保证程序的执行顺序是同步的. 原本是社区的一个规范的构想,被加入到ES6的语言标准里面,比如Chrom,Firefox浏览器已对

[转载] Node.js 笔记(一) nodejs、npm、express安装

感谢原作者: http://blog.csdn.net/haidaochen/article/details/7257655 Windows平台下的node.js安装 直接去nodejs的官网http://nodejs.org/上下载nodejs安装程序,双击安装就可以了 测试安装是否成功: 在命令行输入 node –v 应该可以查看到当前安装的nodejs版本号 简单的例子写一段简短的代码,保存为helloworld.js,大致看下nodejs是怎么用的. 如下:该代码主要是创建一个http服

方便大家学习的Node.js教程(一):理解Node.js

理解Node.js 为了理解Node.js是如何工作的,首先你需要理解一些使得Javascript适用于服务器端开发的关键特性.Javascript是一门简单而又灵活的语言,这种灵活性让它能够经受住时间的考验.函数.闭包等特性使Javascript成为一门适合Web开发的理想语言. 有一种偏见认为Javascript是不可靠的,然而事实并非如此.人们对Javascript的偏见来源于DOM,DOM是浏览器厂商提供的用于Javascript与浏览器交互的API,不同浏览器厂商实现的DOM存在差异.

2015年12月12 Node.js实战(一)使用Express+MongoDB搭建多人博客

序,Node是基于V8引擎的服务器端脚本语言. 基础准备 Node.js: Express:本文用的是3.21.2版本,目前最新版本为4.13.3,Express4和Express3还是有较大区别,可以去官网查看wiki:https://github.com/strongloop/express MongoDB: 一.使用Express搭建一个站点 1 快速开始安装Express Express是Node上最流行的Web开发框架,通过它可以快速开发一个Web应用.全局模式下输入命令: $ npm

Node.js入门(一)

一.Node.js环境配置: http://www.w3cschool.cc/nodejs/nodejs-install-setup.html 二.命令行控制(cmd/命令提示符): 1.读取文件方式: C:\User\UserName> C:\User\UserName>E: //你的node项目文件夹所在的磁盘 E:\>cd\node //我的node文件都放在node文件夹下 E:>node>node server.js 2.交互模式: C:\User\UserName

Node.js 笔记(一) nodejs、npm、express安装(转)

转载地址:http://blog.csdn.net/haidaochen/article/details/7257655 Windows平台下的node.js安装 直接去nodejs的官网http://nodejs.org/上下载nodejs安装程序,双击安装就可以了 测试安装是否成功: 在命令行输入 node –v 应该可以查看到当前安装的nodejs版本号 简单的例子写一段简短的代码,保存为helloworld.js,大致看下nodejs是怎么用的. 如下:该代码主要是创建一个http服务器

部署Node.js项目(CentOS)

摘自:https://help.aliyun.com/document_detail/50775.html Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,用来方便地搭建快速的易于扩展的网络应用.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效,非常适合运行在分布式设备的数据密集型的实时应用.Node.js 的包管理器 npm,是全球最大的开源库生态系统.典型的应用场景包括: 实时应用:如在线聊天,实时通知推送等等(如socke

Node.js笔记(一)

=============node.js  note ========================== 1 NodeJS是一个服务器端JavaScript解释器        apt-get install node node.js 的安装学习请查看下列url:http://www.runoob.com/nodejs/nodejs-install-setup.html  +++++++++++++node.js 安装+++++++++++++++  +安装:  +     sudo apt-