感谢b站教程《Nodejs教程_Nodejs+Koa2入门实战视频教程-2020年更新》
参考:
- 同步异步:https://www.liaoxuefeng.com/wiki/1022910821149312/1023025763380448
- 回调函数callback:
https://blog.csdn.net/rockage/article/details/79513450 - promise:https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544
一、同步、异步
fs模块同时提供了异步和同步的方法。回顾一下什么是异步方法。因为JavaScript的单线程模型,执行IO操作时,JavaScript代码无需等待,而是传入回调函数后,继续执行后续JavaScript代码。比如jQuery提供的getJSON()操作:
$.getJSON('http://example.com/ajax', function (data) {
console.log('IO结果返回后执行...');
});
console.log('不等待IO结果直接执行后续代码...');
而同步的IO操作则需要等待函数返回。
// 根据网络耗时,函数将执行几十毫秒~几秒不等:
var data = getJSONSync('http://example.com/ajax');
同步操作的好处是代码简单,缺点是程序将等待IO操作,在等待时间内,无法响应其它任何事件。而异步读取不用等待IO操作,但代码较麻烦。
同步方法不接收回调函数,如果同步读取文件发生错误,则需要用try…catch捕获该错误。
try {
var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
} catch (err) {
// 出错了
}
异步读文件时,可以直接用回调函数捕捉错误。
fs.readFile('sample.png', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
console.log(data.length + ' bytes');
}
});
由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。
服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。
二、递归解决问题2
问题描述:wwwroot文件夹下面有images css js 以及index.html,找出wwwroot目录下所有目录,然后放在一个数组中
var path = './wwwroot'
var dirArr = [];
fs.readdir(path, (err, data)=>{
if(err) {
console.log(err);
return;
}
// 递归实现
(function getDir(i){
if(i == data.length) { // 执行完成
console.log(dirArr)
return
}
fs.stat(path+'/'+data[i], (error, stats)=>{
if(stats.isDirectory()) {
dirArr.push(data[i])
}
getDir(i+1)
})
})(0)
})
三、新特性
- let和var一样的用来定义变量,而let、const是块作用域
- 属性的简写
var name = 'zhangsan'
var app = {
"name":name
}
console.log(app.name)
// 如果属性名称和变量名一样,则可如下简写
var name = 'zhangsan'
var app = {
name
}
console.log(app.name)
- 方法的简写
var name = 'zhangsan'
var app = {
name,
run:function() {
console.log(`${this.name}在跑步`)
}
}
app.run()
// 简写后
var name = 'zhangsan'
var app = {
name,
run() {
console.log(`${this.name}在跑步`)
}
}
app.run()
- 箭头函数
setTimeout(function(){
console.log('执行')
}, 1000)
// 改写后
setTimeout(()=>{
console.log('执行')
}, 1000)
四、处理异步
4.1 回调函数callback
在大多数编程语言中,函数的形参总是由外往内向函数体传递参数,但在JS里如果形参是关键字"callback"则完全相反,它表示函数体在完成某种操作后由内向外调用某个外部函数。
// callback传异步数据
function getData(callback) {
// ajax
setTimeout(()=>{
var name = 'zhangsan'
callback(name);
}, 1000);
}
// 外部获取异步方法里的数据
getData((aaa)=>{
console.log(aaa)
})
getData的参数为callback函数,callback函数在后期调用时被赋值为了(aaa)=>{console.log(aaa)},将函数传入getData函数中替代callback。因为callback是在getData函数里的,所以输出的name为赋值好的’zhangsan’。
4.2 ES6中新定义的promise解决异步传参数
- promise介绍
我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
function test(resolve, reject) {
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}
这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve(‘200 OK’),如果执行失败,我们将调用reject(‘timeout in ’ + timeOut + ’ seconds.’)。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:
var p1 = new Promise(test);
var p2 = p1.then(function (result) {
console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) { // 失败时用catch
console.log('失败:' + reason);
});
变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:
// 如果成功,执行这个函数:
p1.then(function (result) {
console.log('成功:' + result);
});
当test函数执行失败时,我们告诉Promise对象:
p2.catch(function (reason) {
console.log('失败:' + reason);
});
- 串行执行异步任务
Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。
要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:
job1.then(job2).then(job3).catch(handleError); // 可以统一用一个catch处理错误
其中,job1、job2和job3都是Promise对象。
- 并行执行异步任务Promise.all()
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
- 异步任务容错 Promise.race()
有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1执行较快,Promise的then()将获得结果’P1’。p2仍在继续执行,但执行结果将被丢弃。
4.3 Async、Await
- async用于声明一个异步的function,await用于等待一个异步方法执行完成
- async返回字符串并用await获取(不建议)
async function test(){ // 会自动将返回值包装为promise
return "你好nodejs"
}
console.log(test())
输出:Promise { ‘你好nodejs’ }
若要获得“你好nodejs”这个字符串,而不是promise,则需要用await函数。需要注意await要定义在异步方法中。
async function main() {
var data = await test(); // 获取异步方法里数据
console.log(data);
}
main();
- async返回promise对象并用await获取
async function test() { // 会自动将返回值包装为promise
return new Promise((resolve, reject) => {
setTimeout(()=>{
var name = 'zhangsan'
resolve(name);
}, 1000);
})
}
async function main() {
var data = await test(); // 获取异步方法里数据
console.log(data);
}
main();
- 练习
wwwroot文件夹下面有images css js 以及index.html,找出wwwroot目录下所有目录,然后放在一个数组中
fs = require('fs')
const path = './wwwroot'
let dirArr = [];
// 1. 定义一个isDir方法判断资源是目录还是文件
async function isDir(path)
{
return new Promise((resolve, reject) => {
fs.stat(path, (error, stats)=>{
if(error) {
console.log(error)
reject(error)
return;
}
if(stats.isDirectory()) {
resolve(true);
} else {
resolve(false);
}
})
})
}
// 2. 获取wwwroot
function main() {
// 哪个方法里需要await,就把哪个直接外部方法设置为async
fs.readdir(path, async (err, data) => {
if (err) {
console.log(err)
return
}
for (let i = 0; i < data.length; i++) {
// await语句执行完之后才会执行下面的语句
if (await isDir(path+'/'+data[i])) {
dirArr.push(data[i])
}
}
console.log(dirArr)
})
}
main()