Node的流程控制
什么是流程控制
在Node中,流程控制是让一组异步任务顺序执行的概念,流程控制分为串行
和并行
。
串行示意图:
并行示意图:
串行流程控制
串行的应用场景
串行流程控制是在某些情况下,相关操作必须按顺序执行而产生的一种控制方式。比如我要在一个新目录test
里创建一个新文件test1.txt
,并且在test1.txt
中写入hello world
,然后再创建一个新文件test2.js
读取test1.txt
中的hello word
一样,这里的顺序只能有一个,尽管文件的读写是异步,但是我们只能等待上一步完成,才能继续进行下一步。
实现串行的方法
- 使用定时器
setTimeout
来模拟
这里使用三个定时器来模拟,当第一个定时器回调函数执行后会将第二个定时器挂起等待回调执行,然后执行第二个回调,挂起第三个定时器等待回调执行,最后执行第三个回调,依次输出:
setTimeout(() => {
console.log('I execute first.');
setTimeout(() => {
console.log('I execute next.');
setTimeout(() => {
console.log('I execute last.');
}, 100);
}, 500);
}, 1000);
- 使用
async
库实现
利用第三方开源库async
可以轻松实现顺序执行,只需要把顺序执行的函数放入数组中即可。
使用前先安装async
npm i --save 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);
}
]);
- 手动实现
串行化流程控制本质上是在需要时让回调进场,而不是简单地把它们嵌套起来。这里我们定义一个tasks
,用它来保存每一个异步函数的操作,通过next()
来依次调用,并将结果作为参数传入下一个函数中去,从而保证执行的顺序。
const fs = require('fs');
const request = require('request');
const htmlparser = require('htmlparser');
const configFilename = './rss_feeds.txt';
function checkForRSSFile() {
fs.exists(configFilename, (exists) => {
if (!exists)
return next(new Error(`Missing RSS file: ${configFilename}`));
next(null, configFilename);
});
}
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]);
});
}
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);
});
}
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
];
function next(err, result) {
if (err) throw err;
const currentTask = tasks.shift();
if (currentTask) {
currentTask(result);
}
}
next();
并行流程控制
并行的应用场景
同时请求多个资源、读取多个文件
实现并行的方法
为了让异步任务并行执行,仍然是要把任务放到数组中,但任务的存放顺序无关紧要。每个任务都应该调用处理器函数增加已完成任务的计数值。当所有任务都完成后,处理器函数应该执行后续的逻辑。
用并行化流程控制实现对几个文件中单词频度的计数:
手动实现并行
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());
});