1. 回调地狱
多层回调函数的相互嵌套,就形成了回调地狱。
如下:
setTimeout(() => {//第一层回调函数
console.log('延时1秒后输出')
setTimeout(() => {//第二层回调函数
console.log('再延时2秒后输出')
setTimeout(() => {//第三层回调函数
console.log('再延时3秒后输出')
},3000)
},2000)
},1000)
不断地嵌套,不断地缩进,使代码难以阅读、难以维护。
1.1解决回调地狱的问题
为了解决回调地狱的问题,ES6(ECMAScript2015)中新增了Promise的概念。
1.2Promise的基本概念
①Promise是一个构造函数
● 可以使用new关键词创建Promise实例
● new出来的Promise实例对象,代表一个异步操作
②Promise.prototype原型对象上包含一个 .then()方法
每一次new Promise()构造函数得到的实例对象,都可以通过原型链的方式访问到.then()方法,例如p.then()
③.then()方法用来预先指定成功和失败的回调函数
● p.then(成功的回调函数,失败的回调函数)
● p.then(result => {},error => {})
● 调用.then()方法时,成功的回调函数是必选的、失败的回调函数是可选的
2. 基于回调函数按顺序读取文件内容
按照顺序读取文件夹📂中的文件
//读取文件1.txt
fs.readFile( './files/1.txt ', 'utf8', (err1, r1) =>{
if (err1) return console.log(err1.message) //读取文件1失败
console.log(r1) //读取文件1成功
//读取文件 2.txt
fs.readFile( './files/2.txt ','utf8 ', (err2,r2)=>{
if (err2) return console.log(err2.message) //读取文件2失败
console.log(r2) //读取文件2成功
//读取文件 3.txt
fs.readFile( './files/3.txt','utf8', (err3,r3) =>{
if (err3) return console.log(err3.message)//读取文件3失败
console.log(r3)//读取文件3成功
})
})
})
为保证读取的顺序,读取文件的函数往内又嵌套了两层,但是同时也形成了回调地狱。
3. 基于then-fs读取文件内容
由于node.js官方提供的fs模块仅支持以回调函数的方式读取文件,不支持 Promise 的调用方式。因此,需要先运行如下的命令,安装 then-fs这个第三方包,从而支持我们基于Promise的方式读取文件的内容:
npm install then-fs
3.1. then-fs的基本使用
调用then-fs提供的readFile()方法,可以异步地读取文件的内容,它的返回值是Promise的实例对象。因此可以调用.then()方法为每个Promise异步操作指定成功和失败之后的回调函数。
如下所示:
import thenfs from 'then-fs'
thenfs.readFile('./files/1.txt', 'utf8').then((r1) => {
console.log(r1)
})
thenfs.readFile('./files/2.txt', 'utf8').then((r2) => {
console.log(r2)
})
thenfs.readFile('./files/3.txt', 'utf8').then((r3) => {
console.log(r3)
})
通过then-fs提供的readFile()方法可以读取文件,但是无法保证文件的读取顺序,还需要进一步改造。
3.2. then()方法的特性
如果上一个.then()方法中返回了一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。通过then()方法的链式调用,就解决了回调地狱的问题。
3.3. 基于Promise按顺序读取文件的内容
promise支持链式调用,从而来解决回调地狱的问题。
如下:
thenFs.readFile('./files/1.txt ', 'utf8 ')// 1.返回值是 Promise的实例对象.then((r1) => { //2.通过 .then为第一个 Promise 实例指定成功之后的回调函数
console.log(r1)
return thenFs.readFile( './files/2.txt ', 'utf8')// 3.在第一个.then中返回一个新的 Promise 实例对象})
.then((r2) => {//4.继续调用.then,为上一个.then的返回值(新的 Promise实例)指定成功之后的回调函数
console.log(r2)
return thenFs. readFile('./files/3.txt ', 'utf8 ') // 5.在第二个.then 中再返回一个新的Promise 实例对象})
.then((r3)=>{//6、继续调用.then,为上一个.then 的返回值(新的 Promise 实例)指定成功之后的回调函数
console.log(r3)
}
3.4. 通过.catch捕获错误
在Promise的链式操作中如果发生了错误,可以使用Promise.prototype.catch方法进行捕获和处理:
import thenFs from 'then-fs'
thenFs.readFile('./files/11.txt', 'utf8')
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3)
})
.catch((err) => { console.log(err.message); })
如果不希望前面的错误导致后续的.then无法正常执行,则可以将.catch的调用提前,如下:
import thenFs from 'then-fs'
thenFs.readFile('./files/11.txt', 'utf8')
.catch((err) => {
console.log(err.message);
})
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3)
})
3.5. Promise.all()方法
Promise.all()方法会发起并进行的Promise异步操作,等所有的异步操作全部结束后才会执行下一步的.then操作(等待机制)。如下:
import thenFs from "then-fs";
const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8')
]
Promise.all(promiseArr).then(result => {
console.log(result);
})
3.6. Promise.race()方法
Promise.race()方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就立即执行下一步的.then操作(赛跑机制)。如下:
import thenFs from "then-fs";
const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8')
]
Promise.race(promiseArr).then(result => {
console.log(result);
})
4. 基于Promise封装读文件的方法
方法封装的要求:
● 方法的名称要定义为getFile
● 方法接收一个形参发fpath,表示要读取的文件路径
● 方法的返回值为Promise实例对象
4.1. getFile方法的基本定义
function getFile(fpath){
//方法的返回值为Promise的实例对象
return new Promise()
}
上面代码中最后一行中的 new Promise() 只是创建了一个形式上的异步操作。
4.2. 创建具体的异步操作
如果想要创建具体的异步操作,则需要在new Promise()构造函数期间,传递一个function函数,将具体的异步操作定义到function函数内部。示例代码如下:
import fs from 'fs'
function fetFile(fpath) {
return new Promise(function() {
fs.readFile(fpath, 'utf8', (err, dataStr) => { })
})
}
4.3. 获取.then的两个实参
通过.then()指定的成功和失败的回调函数,可以在function的形参中进行接收,代码如下:
function getFile(fpath) {
//resolve 形参是:调用 getFiles()方法时,通过 .then 指定的“成功的”回调函数
//reject形参是:调用 getFiles()方法时,通过 .then指定的“失败的”回调函数
return new Promise(function(resolve,reject){
fs.readFile(fpath,'utf8',(err,dataStr => {})
})
}
4.4. 调用resolve和reject回调函数
promise 异步操作的结果,可以调用resolve或 reject回调函数进行处理。示例代码如下:
function getFile(fpath) {
//resolve是“成功的”回调函数; reject是“失败的”回调函数
return new Promise(function(resolve,reject){
fs.readFile(fpath,'utf8', (err,dataStr) => {
if(err) return reject(err) // 如果读取失败,则调用"失败的回调函数”
resolve(dataStr) //如果读取成功,则调用“成功的回调函数”
})
)}
//getFile方法的调用过程:
getFile( './files/1.txt ' ).then(成功的回调函数,失败的回调函数)