流是事件的一个实例,stream是基于事件工作的,所以在流对象上具有异步的特征,可以监听事件,触发事件,流在各个阶段的变化都可以被我们实时监听
// var http = require('http')
var https = require('https')
var Promise = require('bluebird')
var cheerio = require('cheerio')
var baseUrl = 'https://www.imooc.com/learn/'
var url = 'https://www.imooc.com/learn/348'
var videoIds = [348, 259, 197, 134, 75]
function filterChapters(html) {
var $ = cheerio.load(html)
var chapters = $('.chapter')
var title = $('hd h2').text()
var number = parseInt($('.js-learn-num').text())
// courseData = {
// title:title,
// number:number,
// videos:[{
// chapterTitle: '',
// videos: [
// title: '',
// id: ''
// ]
// }]
// }
var coursesData = {
title: title,
number: number,
videos: []
}
chapters.each(function (item) {
var chapter = $(this)
var chapterTitle = chapter.find('.chapter-description').text()
var videos = chapter.find('.video').children('li')
var chapterData = {
chapterTitle: chapterTitle,
videos: []
}
videos.each(function (item) {
var video = $(this).find('.J-media-item')
var videoTitle = video.text()
var id = video.attr('href').split('video/')[1]
chapterData.videos.push({
title: videoTitle,
id: id
})
})
coursesData.videos.push(chapterData)
})
return coursesData
}
function printCourseInfo(coursesData) {
coursesData.forEach((courseData) => {
console.log(courseData.number) + '人学过' + courseData.title + '\n'
})
coursesData.forEach(courseData => {
console.log('###' + courseData.title + '\n')
courseData.videos.forEach((item) => {
var chapterTitle = item.chapterTitle
console.log(chapterTitle + '\n')
item.videos.forEach(video => {
console.log('【' + video.id + '】' + video.title + '\n')
})
})
});
}
function getPageAsync(url) {
return new Promise(function (resolve, reject) {
console.log("正在爬取" + url)
https.get(url, function (res) {
var html = ''
res.on('data', function (data) {
html += data
})
res.on('end', function () {
resolve(html)
// var courseData = filterChapters(html)
// printCourseInfo(courseData)
})
}).on('error', function () {
console.log('获取出错')
reject(e)
})
})
}
var fetchCourseArray = []
videoIds.forEach((id) => {
fetchCourseArray.push(getPageAsync(baseUrl + id))
})
Promise.all(fetchCourseArray).then(function (pages) {
//
var courseData = []
pages.forEach(function (html) {
var courses = filterChapters(html)
courseData.push(courses)
})
courseData.sort((a, b) => {
return a.number < b.number
})
printCourseInfo(courseData)
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Promise</title>
<style>
.ball {
width: 40px;
height: 40px;
border-radius: 50%;
}
.ball1 {
background: red;
}
.ball2 {
background: yellow;
}
.ball3 {
background: green;
}
</style>
<script src="./node_modules/bluebird/js/browser/bluebird.js"></script>
</head>
<body>
<div class="ball ball1" style="margin-left: 0;"></div>
<div class="ball ball2" style="margin-left: 0;"></div>
<div class="ball ball3" style="margin-left: 0;"></div>
<script>
var ball1 = document.querySelector('.ball1')
var ball2 = document.querySelector('.ball2')
var ball3 = document.querySelector('.ball3')
// 方法一
// function animate(ball, distance, cb) {
// setTimeout(function () {
// var marginLeft = parseInt(ball.style.marginLeft, 10);
// if (marginLeft === distance) {
// cb && cb();
// } else {
// if (marginLeft < distance) {
// marginLeft++;
// } else {
// marginLeft--;
// }
// ball.style.marginLeft = marginLeft + 'px';
// animate(ball, distance, cb)
// }
// }, 13)
// }
// animate(ball1, 100, function () {
// animate(ball2, 200, function () {
// animate(ball3, 300, function () {
// animate(ball3, 150, function () {
// animate(ball2, 150, function () {
// animate(ball1, 150, function () {})
// })
// })
// })
// })
// })
// 方法二
var Promise = window.Promise
function promiseAnimate(ball, distance) {
return new Promise((resolve, reject) => {
function _animate() {
setTimeout(function () {
var marginLeft = parseInt(ball.style.marginLeft, 10);
if (marginLeft === distance) {
resolve()
} else {
if (marginLeft < distance) {
marginLeft++;
} else {
marginLeft--;
}
ball.style.marginLeft = marginLeft + 'px';
_animate()
}
}, 13)
}
_animate()
})
}
promiseAnimate(ball1, 100)
.then(function () {
return promiseAnimate(ball2, 200)
})
.then(function () {
return promiseAnimate(ball3, 300)
})
.then(function () {
return promiseAnimate(ball3, 150)
})
.then(function () {
return promiseAnimate(ball2, 150)
})
.then(function () {
return promiseAnimate(ball1, 150)
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Promise</title>
<style>
.ball {
width: 40px;
height: 40px;
border-radius: 50%;
}
.ball1 {
background: red;
}
.ball2 {
background: yellow;
}
.ball3 {
background: green;
}
</style>
</head>
<body>
<div class="ball ball1" style="margin-left: 0;"></div>
<div class="ball ball2" style="margin-left: 0;"></div>
<div class="ball ball3" style="margin-left: 0;"></div>
<script>
var ball1 = document.querySelector('.ball1')
var ball2 = document.querySelector('.ball2')
var ball3 = document.querySelector('.ball3')
function animate(ball, distance, cb) {
setTimeout(function () {
var marginLeft = parseInt(ball.style.marginLeft, 10);
console.log(marginLeft);
console.log(distance)
if (marginLeft == distance) {
cb && cb();
} else {
if (marginLeft < distance) {
marginLeft++;
} else {
marginLeft--;
}
ball.style.marginLeft = marginLeft + 'px';
animate(ball, distance, cb)
}
}, 13)
}
animate(ball1, 100, function () {
animate(ball2, 200, function () {
animate(ball3, 300, function () {
animate(ball3, 150, function () {
animate(ball2, 150, function () {
animate(ball1, 150, function () {})
})
})
})
})
})
</script>
</body>
</html>
var http = require('http'); var https = require('https'); //var Promise = require('Promise') var Promise = require('bluebird') var cheerio = require('cheerio'); var baseUrl = 'http://www.imooc.com/learn/'; var videoIds = [348, 259, 197, 134, 75]; function filterChapters(html) { var $ = cheerio.load(html); var chapters = $('.chapter.course-wrap'); var title = $('.hd.clearfix h2').text(); //console.log('###课程标题###:' + title); //var number = parseInt($('.meta-value.js-learn-num').text()); //var numberHtml = getHttpsPageAsync('https://www.imooc.com/course/AjaxCourseMembers?ids=348'); //var number = JSON.parse(getHttpsPageAsync('https://www.imooc.com/course/AjaxCourseMembers?ids=348')).data[0].numbers; //console.log('###课程人数###:' + numberHtml); var number = 0; //var courseData = []; var courseData = { title: title, number: number, videos: [] } chapters.each(function(item) { var chapter = $(this) var chapterTitle = chapter.find('h3').text(); //console.log('test:' + chapterTitle); var videos = chapter.find('.video').children('li'); var chapterData = { chapterTitle: chapterTitle, videos: [] } videos.each(function(item) { var video = $(this).find('.J-media-item'); var videoTitle = video.text(); //console.log('test:' + videoTitle); var id = video.attr('href').split('video/')[1]; //console.log('test:' + id); chapterData.videos.push({ title: videoTitle, id: id }) }) courseData.videos.push(chapterData); }) return courseData; } function printCourseInfo(coursesData) { coursesData.forEach(function(courseData) { console.log(courseData.number + ' 人学过' + courseData.title + '\n'); }) coursesData.forEach(function(courseData) { console.log('###' + courseData.title + '\n'); courseData.videos.forEach(function(item) { var chapterTitle = item.chapterTitle; console.log(chapterTitle.trim() + '\n'); item.videos.forEach(function(video) { console.log('【' + video.id + '】' + video.title.trim() + '\n'); }) }) }); } //生成爬取任务,将html作为Promise的resolve方法传递的值 function getPageAsync(url) { return new Promise(function(resolve, reject) { console.log('正在爬取 ' + url) http.get(url, function(res) { var html = ''; res.on('data', function(data) { html += data; }); res.on('end', function() { resolve(html) //var courseData = filterChapters(html); //printCourseInfo(courseData); }); }).on('error', function(e) { reject(e); console.log('获取课程数据出错'); }); }) } function getHttpsPageAsync(url) { //return new Promise(function(resolve, reject) { console.log('正在爬取 ' + url) https.get(url, function(res) { var html = ''; res.on('data', function(data) { html += data; }); console.log('https爬取' + html); res.on('end', function() { return html; //resolve(html) //var courseData = filterChapters(html); //printCourseInfo(courseData); }); }).on('error', function() { //reject(e); console.log('获取课程数据出错'); }); //}) } var fetchCourseArry = [] videoIds.forEach(function(id) { fetchCourseArry.push(getPageAsync(baseUrl + id)) }) //等待所有网页爬取任务完成后再进行filter操作,这时then里传递的是所有任务的resolve中传递值的数组? Promise .all(fetchCourseArry) .then(function(pages) { var coursesData = [] pages.forEach(function(html) { var courses = filterChapters(html) coursesData.push(courses) }) coursesData.sort(function(a, b) { return a.number < b.number; }) printCourseInfo(coursesData); })
成果 (๑•̀ㅂ•́)و,也算入一点nodejs的门了吧, 感谢老师
promise是一个对象,通过then方法可以链式编程,并且是在异步请求的基础上,实现了同步操作then.
promise库
bluebird, Q,
then.js, es6-promise,
ypromise, async,
native-promise-only
//之前的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>promise animation</title>
<style>
.ball {
width: 40px;
height: 40px;
border-radius: 50%;
}
.ball1 {
background-color: red;
}
.ball2 {
background-color: green;
}
.ball3 {
background-color: yellow;
}
</style>
</head>
<body>
<div>
<div class="ball ball1" style='margin-left: 0;'></div>
<div class="ball ball2" style='margin-left: 0;'></div>
<div class="ball ball3" style='margin-left: 0;'></div>
</div>
</body>
</html>
<script>
var ball1 = document.querySelector('.ball1')
var ball2 = document.querySelector('.ball2')
var ball3 = document.querySelector('.ball3')
function animate(ball, distance, cb) {
setTimeout(function () {
// console.log(ball.style.marginLeft);
var marginLeft = parseInt(ball.style.marginLeft, 10)
// console.log(marginLeft);
if (marginLeft === distance) {
// 期望的点
cb && cb();
// animate(ball, distance, cb)
} else {
if (marginLeft > distance) {
// console.log(111);
marginLeft--;
} else {
marginLeft++;
}
// 改变样式
ball.style.marginLeft = marginLeft + 'px';
animate(ball, distance, cb)
}
},10)
}
//
animate(ball1, 100, function () {
animate(ball2, 200, function () {
animate(ball3, 300, function () {
animate(ball3, 150, function () {
animate(ball2, 150, function () {
animate(ball1, 150, function () {
})
})
})
})
})
})
</script>
一、stream种类
1、readable:
(1)reasume:流动模式
(2)pause:暂停模式
2、writable
3、duplex:双攻流,可读可写
tcp socket、zlip、queptal
4、transform:双攻流,可读可写,转换流
不保存数据
zlip、queptal
二、pipe
1、pipe方法会自动监听data,err事件。
2、pipe方法可以自动控制后端压力。
三、上一节的代码可以简写成
var fs = require('fs'); fs.createReadStream('websocket.gif').pipe(fs.createWriteStream('websoket-pipe.gif'));
一、cat *.js | grep http 过滤包含Http的内容
二、buffer:保存原始数据
流:暂存和移动数据
三、涉及流的模块:http、文件系统、压缩模块、tcpsocket、紫禁城标准式输出。
流是buffer的形式存在。
四、拷贝时防爆仓处理
1、readStream的data中添加if。
2、writeSteam的drain处理。
var fs = require('fs'); var readStream = fs.createReadStream('websocket.gif'); var writeSteam = fs.createWriteStream('websocket-stream.gif'); readStream.on('data', function(chunk) { if(writeSteam.write(chunk) === false) { console.log('still cached'); readStream.pause(); } // writeSteam.write(chunk); }); readStream.on('end', function() { writeSteam.end(); }); writeSteam.on('drain', function() { console.log('data drains'); readStream.resume(); })
一、buffer: tcp/图像/文件/网络
1、poolSize: 内存载体的容量
2、isBuffer
3、compare:判断buffer对象的相对位置,字符串排序
4、isEncoding:nodeJs是否支持某种编码
5、concat:连接buffer对象
6、byteLength:获得指定编码下字节长度。
二、buffer源码,1000行左右
1、process.binding使javascript代码和c++代码能够进行交互。
2、smalloc:操作内存和分配内存的对象
smalloc.alloc:生成内存的方法
一、buffer和stream
1、buffer
(1)缓冲,nodeJs处理二进制的数据。js字符串数据‘utf-8’格式。
(2)全局的,不需要require引入。是一个构造函数,有自己的属性和方法。
(3)实例化Buffer
①new 一个实例化对象
new Buffer('hello');
② 内存空间实例化
var buf = new Buffer(8); // 8:内存空间的大小
③ 数组方式实例化的对象,可以直接用下标访问。通过下标取到的值是直接取整,如果原先是小数,取出来的就是整数了。
var buf = new Buffer([1, 2, 3, 4]);
一、https协议是在http协议的基础上添加了SSL和TLS
二、https模块主要是处理加密访问的。搭建https的时候需要ssl证书。
三、并发控制,同时去爬。Promise.all();
一、异步操作解决方案:
回调
事件机制
对事件进行监听
对某个异步操作增加事件的触发,包括订阅者、发布者等观察者模式
promise
二、Promise是一个对象,与其他javascript对象没有其他区别
三、Promise对象的三种状态
1、未完成 pending
2、已完成 fufilled
3、 失败 rejected
四、jquery中的promise,then返回的不是新的promise,只是改变了promise的状态。
五、promise then 方法:
1、then方法必须是返回一个promise对象。
2、then接收2个参数,这2个参数都可以省略。
3、promise会保证then的回调顺序。
一、webstorm列编辑,多行编辑快捷键:
按住alt键鼠标选择要修改的字符串,然后输入文字就会编辑多行。
二、动画一般一秒钟60帧,是比较流畅的帧率。
三、安装bluebird,aSuncat:现在的nodejs不安装bluebird就能用promise(es6语法)。
1、cd promise
2、npm install bluebird
<!doctype><html> <head> <title>Promise animation</title> <style type="text/css"> .ball { width: 40px; height: 40px; border-radius: 20px; } .ball1 { background: red; } .ball2 { background: yellow; } .ball3 { background: green; } </style> <script type="text/javascript" src="./node_modules/bluebird/js/browser/bluebird.js"></script> </head> <body> <div class="ball ball1" style="margin-left: 0;"></div> <div class="ball ball2" style="margin-left: 0;"></div> <div class="ball ball3" style="margin-left: 0;"></div> <script type="text/javascript"> var ball1 = document.querySelector('.ball1') var ball2 = document.querySelector('.ball2') var ball3 = document.querySelector('.ball3') function animate(ball, distance, cb) { setTimeout(function() { var marginLeft = parseInt(ball.style.marginLeft, 10) if(marginLeft === distance){ cb && cb() }else{ if(marginLeft < distance){ marginLeft++ }else{ marginLeft-- } ball.style.marginLeft = marginLeft + 'px' animate(ball, distance, cb) } }, 13) } /*animate(ball1, 100, function() { animate(ball2, 200, function() { animate(ball3, 300, function() { animate(ball3, 150, function() { animate(ball2, 150, function() { animate(ball1, 150, function() { }) }) }) }) }) })*/ var promise = window.Promise function promiseAnimate(ball, distance) { return new Promise(function(resolve, reject) { function _animate() { setTimeout(function() { var marginLeft = parseInt(ball.style.marginLeft, 10) if(marginLeft === distance){ resolve() }else{ if(marginLeft < distance){ marginLeft++ }else{ marginLeft-- } ball.style.marginLeft = marginLeft + 'px' _animate() } }, 13) } _animate() }) } promiseAnimate(ball1, 100) .then(function() { return promiseAnimate(ball2, 200) }) .then(function() { return promiseAnimate(ball3, 300) }) .then(function() { return promiseAnimate(ball3, 150) }) .then(function() { return promiseAnimate(ball2, 150) }) .then(function() { return promiseAnimate(ball1, 150) }) </script> </body></html>
用promise重构爬虫代码- new一个promise对象爬取每一节课程信息
Pipe左边是可读流,右边是输出流
Node.js 中有四种基本的流类型:
Readable - 可读的流 (例如 fs.createReadStream()).
Writable - 可写的流 (例如 fs.createWriteStream()).
Duplex - (双工流)可读写的流 (例如 net.Socket).
Transform - (转换流)在读写过程中可以修改和变换数据的 Duplex 流 (例如 zlib.createDeflate()).
Buffer的实例方法还有:
pollsize:大小;
isBuffer:判断是否为buffer类型;
compare:判断相对位置;
isEncoding:是否支持某种编码;
concat:连接创建为新的buffer对象;
byteLength:获得指定编码下的字符串所占的字节数。
let http = require('https');
let baseUrl = 'https://www.imooc.com/learn/';
let learnNumber_baseUrl = 'https://www.imooc.com/course/AjaxCourseMembers?ids=';
let cheerio = require('cheerio');
let videosId = [728,637,348,259,197,134,75];
function filerChapters(pageData){
let html = pageData.html;
let $ = cheerio.load(html);
let chapters = $('.chapter');
let courseData = {
title:$('.hd h2').text(),
number:pageData.number,
id:$('.person-num').attr('href').split('/')[2],
videos:[]
};
chapters.each(function(item){
let chapter = $(this);
let chapterTitle = chapter.find('h3').text();
let videos = chapter.find('.video').children('li');
let chapterData = {
chapterTitle:chapterTitle,
videos:[]
};
videos.each(function(item){
let video = $(this).find('.J-media-item');
let videoTitle = video.text().trim();
let id = video.attr('href').split('video/')[1];
let videoData = {
title:videoTitle,
id:id
};
chapterData.videos.push(videoData);
});
courseData.videos.push(chapterData);
});
return courseData;
}
function printCourseData(coursesData){
coursesData.forEach(function(courseData){
console.log('\n');
console.log(' ######### '+courseData.title + ' [学习人数:' + courseData.number + '] #########\n');
courseData.videos.forEach(function(item){
let chapterTitle = item.chapterTitle;
console.log(chapterTitle );
item.videos.forEach(function(video){
console.log(' [' + video.id+ ']' + video.title.trim().split('(')[0]);
});
});
});
}
function getPageAsync(url){
return new Promise(function(resolve, reject){
http.get(url,function(res){
let html = '';
res.on('data',function(data){
html += data;
});
res.on('end',function(){
resolve(html);
});
}).on('error',function(){
console.log('error');
});
});
}
function getLearnDataAsync(html){
return new Promise(function(resolve,reject){
let $ = cheerio.load(html);
let id = $('.person-num').attr('href').split('/')[2];
let pageData = {
html:html,
number:0
};
let db = '';
http.get(learnNumber_baseUrl+id,function(res){
res.on('data',function(data){
db += data;
db = JSON.parse(db);
pageData.number = parseInt(db.data[0].numbers,10);
});
res.on('end',function(){
resolve(pageData);
});
}).on('error',function(){
console.log('error');
});
});
}
let promiseList = [];
// let coursesDataPromises = [];
videosId.forEach(function(id){
promiseList.push(getPageAsync(baseUrl+id).then(function(html){
return getLearnDataAsync(html);
}));
});
Promise
.all(promiseList)
.then(function(pagesData){
let coursesData = [];
pagesData.forEach(function(pageData){
coursesData.push(filerChapters(pageData));
});
printCourseData(coursesData);
});
(一)Promise 1. ES6的Promise语言标准 2. Promise/A+规范 (二)Promise使用场景 1. 是一种异步的实践方案 2. 特别是Callback Hell, 可以用同步的方式写异步代码 (三) Promise的三种状态 1. pending 未完成 2. fulfilled 已完成 3. rejected 失败 (1->2, 1->3 正确) (2->1, 3->1, 2->3 错误) 总结: 只能又未完成变为已完成或失败, 且不可逆, 改变只能一次, 不存在即已完成同时失败
说一下我的理解,Promise没有把异步变同步,只是以同步的方式来写异步,使用promise,当代码执行到resolve时跳到下一步的then方法中依次执行,执行到reject时跳到catch方法依次执行;上一步then方法中返回的值可以是一个新的Promise也可以是某一固定值,为新的Promise时会根据其resolve和reject来进行下一步的代码执行,当为固定值时会把该值传给下一步的then方法参数使用。
Ajax解决的是网页异步刷新问题,完全可以在Promise中嵌套使用ajax。
<div class="ball ball1" style="margin-left: 0"></div>
<div class="ball ball2" style="margin-left: 0"></div>
<div class="ball ball3" style="margin-left: 0"></div>
<script>
var ball1 = document.querySelector(".ball1")
var ball2 = document.querySelector(".ball2")
var ball3 = document.querySelector(".ball3")
var Promise = window.Promise
function promiseAnimate(ball,distance){
return new Promise(function(resolve,reject){
function _animate(){
setTimeout(function(){
var marginLeft = parseInt(ball.style.marginLeft,10)
if(marginLeft === distance){
resolve()
}else{
if(marginLeft < distance){
marginLeft ++
}else{
marginLeft --
}
ball.style.marginLeft = marginLeft + 'px'
_animate()
}
},13)
}
_animate()
})
}
promiseAnimate(ball1,100)
.then(function(){
return promiseAnimate(ball2,200)
})
.then(function(){
return promiseAnimate(ball3,300)
})
.then(function(){
return promiseAnimate(ball3,150)
})
.then(function(){
return promiseAnimate(ball2,150)
})
.then(function(){
return promiseAnimate(ball1,150)
})
let http = require('http'); let baseUrl = 'http://www.imooc.com/learn/'; let learnNumber_baseUrl = 'http://www.imooc.com/course/AjaxCourseMembers?ids='; let cheerio = require('cheerio'); let videosId = [728,637,348,259,197,134,75]; function filerChapters(pageData){ let html = pageData.html; let $ = cheerio.load(html); let chapters = $('.chapter'); let courseData = { title:$('.hd h2').text(), number:pageData.number, id:$('.person-num').attr('href').split('/')[2], videos:[] }; chapters.each(function(item){ let chapter = $(this); let chapterTitle = chapter.find('h3').text(); let videos = chapter.find('.video').children('li'); let chapterData = { chapterTitle:chapterTitle, videos:[] }; videos.each(function(item){ let video = $(this).find('.J-media-item'); let videoTitle = video.text().trim(); let id = video.attr('href').split('video/')[1]; let videoData = { title:videoTitle, id:id }; chapterData.videos.push(videoData); }); courseData.videos.push(chapterData); }); return courseData; } function printCourseData(coursesData){ coursesData.forEach(function(courseData){ console.log('\n'); console.log(' ######### '+courseData.title + ' [学习人数:' + courseData.number + '] #########\n'); courseData.videos.forEach(function(item){ let chapterTitle = item.chapterTitle; console.log(chapterTitle ); item.videos.forEach(function(video){ console.log(' [' + video.id+ ']' + video.title.trim().split('(')[0]); }); }); }); } function getPageAsync(url){ return new Promise(function(resolve, reject){ http.get(url,function(res){ let html = ''; res.on('data',function(data){ html += data; }); res.on('end',function(){ resolve(html); }); }).on('error',function(){ console.log('error'); }); }); } function getLearnDataAsync(html){ return new Promise(function(resolve,reject){ let $ = cheerio.load(html); let id = $('.person-num').attr('href').split('/')[2]; let pageData = { html:html, number:0 }; let db = ''; http.get(learnNumber_baseUrl+id,function(res){ res.on('data',function(data){ db += data; db = JSON.parse(db); pageData.number = parseInt(db.data[0].numbers,10); }); res.on('end',function(){ resolve(pageData); }); }).on('error',function(){ console.log('error'); }); }); } let promiseList = []; // let coursesDataPromises = []; videosId.forEach(function(id){ promiseList.push(getPageAsync(baseUrl+id).then(function(html){ return getLearnDataAsync(html); })); }); Promise .all(promiseList) .then(function(pagesData){ let coursesData = []; pagesData.forEach(function(pageData){ coursesData.push(filerChapters(pageData)); }); printCourseData(coursesData); });
新增于: v0.1.93
成功调用 fs.createReadStream()
会返回一个新的 fs.ReadStream
对象。
fs.ReadStream
对象都是可读流。
新增于: v0.1.93
当 fs.ReadStream
底层的文件描述符被关闭时触发。
新增于: v0.1.93
fd
<integer> ReadStream
使用的整数型文件描述符。
当 fs.ReadStream
' 的文件描述符被打开时触发。
新增于: v9.11.0
当 fs.ReadStream
已准备好被使用时触发。
'open'
之后立即触发。