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/'
剩下的代码不一一解释,大致做了下面工作:
- 先读取指定文件夹的全部文件 (
fs.readdir) - 使用
async.each遍历获取的结果 - 推断每一个文件的 stat 是不是文件夹 (
fs.stat)- 若是,检查符不符合条件,符合则进入下一轮递归 (
readFiles) - 若否,检查符不符合条件,符合则加入到终于结果的数组中 (
res.push())
- 若是,检查符不符合条件,符合则进入下一轮递归 (
- 反复 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 仍未包括 id=harmony:parameter_default_values" style="background-color:transparent; color:rgb(51,51,51); text-decoration:none; outline:0px; line-height:inherit; border-bottom-width:2px; border-bottom-style:solid; border-bottom-color:rgb(225,225,225)">函数參数默认值
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 里增加了一个新特性——Generator。Koa 已经開始使用
generator 来构建项目。详细怎么用,下一篇说吧。
长按图片识别图中二维码(或搜索微信公众号FrontEndStory)关注“前端那些事儿”。带你了解最新的前端技术。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" style="border:none; max-width:100%">
Node.js 101(2): Promise and async的更多相关文章
- 进击Node.js基础(二)promise
一.Promise—Promise似乎是ES6中的规范 PROMISE的语言标准,PROMISE/A+规范,如何使用,在什么场景下使用 Promise时JS对异步操作场景提出的解决方案(回调,观察者模 ...
- 10慕课网《进击Node.js基础(一)》初识promise
首先用最简单的方式实现一个动画效果 <!doctype> <html> <head> <title>Promise animation</titl ...
- 进击Node.js基础(二)
一.一个牛逼闪闪的知识点Promise npm install bluebird 二.Promise实例 ball.html <!doctype> <!DOCTYPE html> ...
- 07慕课网《进击Node.js基础(一)》HTTP小爬虫
获取HTML页面 var http = require('http') var url='http://www.imooc.com/learn/348' http.get(url,function(r ...
- node.js学习(二)--Node.js控制台(REPL)&&Node.js的基础和语法
1.1.2 Node.js控制台(REPL) Node.js也有自己的虚拟的运行环境:REPL. 我们可以使用它来执行任何的Node.js或者javascript代码.还可以引入模块和使用文件系统. ...
- node.js学习(1)
新建便笺 3 node.js学习(1) 1)安装 http://nodejs.org/download/下载. 2)编写一个案例 var http=require("http"); ...
- node.js 初学(二)—— 搭建注册/登录服务器
node.js 初学(二)—— 搭建注册/登录服务器 理论上来说,代码实现在理论和实际上是一样的.但实际上来说,他们不是 做一个最简单的用户注册登录功能 1.接口定义: 注册:/user?act=re ...
- Node.js系列——(4)优势及场景
背景 之前几篇系列文章简单介绍了node.js的安装配置及基本操作: Node.js系列--(1)安装配置与基本使用 Node.js系列--(2)发起get/post请求 Node.js系列--(3) ...
- 02慕课网《进击Node.js基础(一)》——CommonJs标准
是一套规范管理模块 每个js 为一个模块,多个模块作为一个包 node.js和Couchdb是对其的实现: 不同于jQuery 模块:定义.标识.引用(地址/模块名称) 模块类型: 核心模块http ...
随机推荐
- luogu2770 航空路线问题
前置技能:HDU3376 Matrix Again 所以看到这个题,我们也会想着用最大费用最大流解决,因为从起点飞到终点再飞回来,就等于从起点飞两次到终点且这两次飞行除了起点终点之外没有访问超过一次的 ...
- Android学习2013年10月11日
1.LinearLayout http://www.cnblogs.com/salam/archive/2010/10/20/1856793.html LinearLayout是线性布局控件,它包含的 ...
- python mock模块使用(一)
什么是mock unittest.mock是一个用于在Python中进行单元测试的库,Mock翻译过来就是模拟的意思,顾名思义这个库的主要功能是模拟一些东西. 它的主要功能是使用mock对象替代掉指定 ...
- 【dp】E. Selling Souvenirs
http://codeforces.com/contest/808/problem/E 题意:给定n个重量为可能1,2,3的纪念品和各自的价值,问在背包总重量不超过m的条件下总价值最大为多少. 其中1 ...
- SQLSERVER金额转换成英文大写的函数
CREATE FUNCTION [dbo].[f_num_eng] (@num numeric(15,2)) RETURNS varchar(400) WITH ENCRYPTION AS BEGIN ...
- 从jmm模型漫谈到happens-befor原则
首先,代码都没有用ide敲,所以不要在意格式,能看懂就行jmm内存模型: jmm是什么? jmm说白了就是定义了jvm中线程和主内存之间的抽象关系的一种模型,也就是线程之间的共享变量存储在主内存,而每 ...
- java多线程调试
1. 多线程调试 https://blog.csdn.net/bramzhu/article/details/52367052 https://www.jb51.net/article/129632. ...
- (二)Commonjs规范与模块化
在之前的学习中我们使用require()来引入我们需要的包,这其实就是模块化,各模块相互独立,可以通过某种方式引入别的模块.而这些引入方式都是遵循一定的规范的,这就是CommonJS规范. 一.Com ...
- 洛谷——P2820 局域网
P2820 局域网 题目背景 某个局域网内有n(n<=100)台计算机,由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成 ...
- apache移植
我下载的是httpd-2.2.9.tar.gz 1. 解压httpd-2.2.9.tar.gz到/mnt/apps目录下.tar -zxvf httpd-2.2.9.tar.gz 2. 建立与http ...