NodeJS入门 0x4 异步编程(3)作用域,流程控制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/funkstill/article/details/85338980

异步开发的难题

    在创建异步程序时,你必须密切关注程序的执行流程,并瞪大眼睛盯着程序的状态:事件轮询的条件、程序变量,以及其他随着程序逻辑执行而发生变化的资源。

     作用域是如何导致 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 [email protected]
npm install --save [email protected]

//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 这三个。 

猜你喜欢

转载自blog.csdn.net/funkstill/article/details/85338980