异步开发的难题
在创建异步程序时,你必须密切关注程序的执行流程,并瞪大眼睛盯着程序的状态:事件轮询的条件、程序变量,以及其他随着程序逻辑执行而发生变化的资源。
作用域是如何导致 bug 出现的
function asyncFunction(callback) {
setTimeout(callback, 200);
}
let color = 'blue';
asyncFunction(() => {
console.log(`The color is ${color}`);//200ms执行
});
color = 'green';
用 JavaScript 闭包可以“冻结” color 的值——用匿名函数保留全局变量的值
function asyncFunction(callback) {
setTimeout(callback, 200);
}
let color = 'blue';
(color => {
asyncFunction(() => {
console.log('The color is', color);
});
})(color);
color = 'green';
异步逻辑的顺序化
让一组异步任务顺序执行的概念被 Node 社区称为流程控制。这种控制分为两类: 串行和并行。
何时使用串行流程控制
可以使用回调让几个异步任务按顺序执行,但如果任务很多,必须组织一下,否则过多的回调嵌套会把代码搞得很乱。
setTimeout(() => {
console.log('I execute first.');//用时1秒
setTimeout(() => {
console.log('I execute next.');//用时半秒
setTimeout(() => {
console.log('I execute last.');//用时十分之一秒
}, 100);
}, 500);
}, 1000);
也可以用 Async 这样的流程控制工具执行这些任务。 Async 用起来简单直接,并且它的代码量很小(经过缩小化和压缩后只有 837 个字节)。
npm install async
//给 Async 一个函数数组,让它一个接一个地执行
const async = require('async');
async.series([
callback => {
setTimeout(() => {
console.log('I execute first.');
callback();
}, 1000);
},
callback => {
setTimeout(() => {
console.log('I execute next.');
callback();
}, 500);
},
callback => {
setTimeout(() => {
console.log('I execute last.');
callback();
}, 100);
}
]);
尽管这种用流程控制实现的版本代码更多,但通常可读性和可维护性更强。你一般也不会一直用流程控制,但当碰到想要躲开回调嵌套的情况时,它就会是改善代码可读性的好工具。
实现串行化流程控制
为了用串行化流程控制让几个异步任务按顺序执行,需要先把这些任务按预期的执行顺序放到一个数组中。
串行化流程控制Demo
mkdir listing_217
cd listing_217
npm init -y
npm install --save request@2.60.0
npm install --save htmlparser@1.7.7
//index.js
const fs = require('fs');
const request = require('request');
const htmlparser = require('htmlparser');
const configFilename = './rss_feeds.txt';
//任务1:检查RSS文件
function checkForRSSFile(){
fs.exists(configFilename,(exists)=>{
if(!exists)
//有错误就尽早返回
return next(new Error(`Missing RSS fiile:${configFilename}`));
next(null,configFilename);
});
}
//任务2:读文件解析URL
function readRSSFile(configFilename){
fs.readFile(configFilename,(err,feedList)=>{
if(err) return next(err);
feedList = feedList
.toString()
.replace(/^\s+|\s+$/g,'')
.split('\n');
const random = Math.floor(Math.random()*feedList.length);
next(null,feedList[random]);
});
}
//任务3::发送请求获取数据
function downloadRSSFeed(feedUrl){
request({uri:feedUrl},(err,res,body)=>{
if(err) return next(err);
if(res.statusCode!==200)
return next(new Error('Abnormal response status code'));
next(null,body);
});
}
//任务4:解析数据
function parseRSSFeed(rss){
const handler = new htmlparser.RssHandler();
const parser = new htmlparser.Parser(handler);
parser.parseComplete(rss);
if(!handler.dom.items.length)
return next(new Error('No RSS items found'));
const item = handler.dom.items.shift();
console.log(item.title);
console.log(item.link);
}
//把所有要做的任务顺序添加到一个数组中
const tasks = [
checkForRSSFile,
readRSSFile,
downloadRSSFeed,
parseRSSFeed
];
//负责执行任务的next函数
function next(err,result){
if(err) throw err;
const currentTask = tasks.shift();
if(currentTask){
currentTask(result);//执行当前任务
}
}
next();//开始任务的串行化执行
如果你自己没有预订源,可以试一下 Node 博客,地址是 http://blog.nodejs.org/feed/。
实现并行化流程控制
为了让异步任务并行执行,仍然是要把任务放到数组中,但任务的存放顺序无关紧要。每个任务都应该调用处理器函数增加已完成任务的计数值。当所有任务都完成后,处理器函数应该执行后续的逻辑。
mkdir listing_218
cd listing_218
mkdir text
//word_count.js
const fs = require('fs');
const tasks = [];
const wordCounts = {};
const filesDir = '../text';
let completedTasks = 0;
function checkIfComplete(){
completedTasks++;
if(completedTasks===tasks.length){
//当全部任务结束,列出每个单词及次数
for(let index in wordCounts){
console.log(`${index}:${wordCounts[index]}`);
}
}
}
function addWordCount(word){
wordCounts[word]=(wordCounts[word])?wordCounts[word]+1:1;
}
function countWordsInText(text){
const words = text
.toString()
.toLowerCase()
.split(/\W+/)
.sort();
words
.filter(word=>word)
.forEach(word=>addWordCount(word));
}
fs.readdir(filesDir,(err,files)=>{
if(err) throw err;
files.forEach(file=>{
const task = (file=>{
return ()=>{
fs.readFile(file,(err,text)=>{
if(err) throw err;
countWordsInText(text);
checkIfComplete();
});
};
})(`${filesDir}/${file}`);
tasks.push(task);
});
tasks.forEach(task=>task());
});
利用社区里的工具
社区中的很多附加模块都提供了方便好用的流程控制工具。其中比较流行的有 Async、 Step和 Seq 这三个。