多层回调函数的相互嵌套,就形成了回调地狱。示例代码如下:
setTimeout(() => { //第 1 层回调函数
console.log('延时 1 秒后输出')
setTimeout(() => { //第 2 层回调函数
console.log('再延时 2 秒后输出')
setTimeout(() => { // 第 3 层回调函数
console.log('再延时 3 秒后输出')
}, 3000)
}, 2000)
}, 1000)
回调地狱的缺点:
为了解决回调地狱的问题,ES6
(ECMAScript 2015)中新增了 Promise
的概念。
① Promise 是一个构造函数
② Promise.prototype 上包含一个 .then() 方法
③ .then() 方法用来预先指定成功和失败的回调函数
npm init -y
"type":"module",
flies文件夹
,并在文件夹下新建1.txt
,2.txt
,3.txt
三个文件,内容分别为111,222,33302m.js
并调用 node .\02m.js
运行import fs from 'fs'
//读取文件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成功
})
})
})
由于 node.js 官方提供的 fs 模块仅支持
以回调函数的方式
读取文件,不支持Promise 的调用方式
。因此,需要先运行如下的命令,安装 then-fs
这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容:
npm install then-fs
调用 then-fs 提供的 readFile()
方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象
。因此可以调用 .then()
方法为每个 Promise 异步操作指定成功
和失败
之后的回调函数。示例代码如下:
注意:上述的代码无法保证文件的读取顺序
,需要做进一步的改进!
/**
* 基于Promise的方式读取文件
*/
import thenFs from 'then-fs'
// 注意 .then()中失败的回调函数是可选的,可以被省略
thenFs.readFile('./files/1.txt', 'utf8').then(r1 => { console.log(r1); }, err1 => { console.log(err1.message); })
thenFs.readFile('./files/2.txt', 'utf8').then(r2 => { console.log(r2); }, err2 => { console.log(err2.message); })
thenFs.readFile('./files/3.txt', 'utf8').then(r3 => { console.log(r3); }, err3 => { console.log(err3.message); })
如果上一个 .then() 方法中返回了一个新的Promise 实例对象
,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用
,就解决了回调地狱的问题。
Promise 支持链式调用
,从而来解决回调地狱的问题。示例代码如下:
import thenFs from "then-fs";
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);
})
在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch
方法进行捕获和处理:
import thenFs from "then-fs";
thenFs.readFile('./files/11.txt', 'utf8') //文件不存在导致读取失败,后面的3个 .then 都不执行
.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 => { // 捕获第 1 行 发生的错误,并输出错误的信息
console.log(err.message); // 由于错误已经被及时处理,不影响后续 .then 的正常执行
})
.then(r1 => {
console.log(r1); // 输出 undefined
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then(r2 => {
console.log(r2); //输出 222
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then(r3 => {
console.log(r3); //输出 333
})
Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后
才会执行下一步的 .then 操作(等待机制)。示例代码如下:
注意:
数组中 Promise 实例的顺序,就是最终结果的顺序!
import thenFs from "then-fs";
// 1.定义一个数组,存放三个读取文件的异步操作
const promiseArr = [
thenFs.readFile('./files/11.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8'),
]
// 2.将 Promise 的数组,作为 Promise.all()的参数
Promise.all(promiseArr)
.then(([r1, r2, r3]) => { // 2.1 所有文件读取成功(等待机制)
console.log(r1, r2, r3);
})
.catch(err => { // 2.2 捕获 Promise 异步操作中的错误
console.log(err.message);
})
Promise.race() 方法会发起并行
的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的
.then 操作(赛跑机制)。示例代码如下:
import thenFs from "then-fs";
// 1.定义一个数组,存放三个读取文件的异步操作
const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8'),
]
// 2.将 Promise 的数组,作为 Promise.arce()的参数
Promise.race(promiseArr)
.then((r) => { // 2.1 只要任何一个异步操作完成,就立即执行成功的回调函数(赛跑机制)
console.log(r);
})
.catch(err => { // 2.2 捕获 Promise 异步操作中的错误
console.log(err.message);
})
方法的封装要求:
① 方法的名称要定义为 getFile
② 方法接收一个形参
fpath
,表示要读取的文件的路径
③ 方法的返回值
为 Promise 实例对象
注意:第 5 行代码中的 new Promise() 只是创建了一个形式上的异步操作。
// 1.方法的名称为 getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
// 3.方法的返回值是 Promise 的实例对象
return new Promise()
}
如果想要创建具体的异步操作
,则需要在new Promise() 构造函数期间,传递一个function 函数,将具体的异步操作定义到 function 函数内部
。示例代码如下:
// 1.方法的名称为 getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
// 3.方法的返回值是 Promise 的实例对象
return new Promise(function () {
// 4.下面这行代码,表示这是一个具体的、读取文件的异步操作
fs.readFile(fpath, 'utf8', (err, dataStr) => { })
})
}
通过 .then() 指定的成功
和失败
的回调函数,可以在 function 的形参中
进行接收,示例代码如下:
// 1.方法的名称为 getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
// 3.方法的返回值是 Promise 的实例对象
//resolve 形参是:调用getFile() 方法时,通过 .then 指定的 "成功的" 回调函数
//reject 形参是:调用getFile() 方法时,通过 .then 指定的 "失败的" 回调函数
return new Promise(function (resolve,reject) {
// 4.下面这行代码,表示这是一个具体的、读取文件的异步操作
fs.readFile(fpath, 'utf8', (err, dataStr) => { })
})
}
// getFile 方法的调用过程
getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)
Promise 异步操作的结果
,可以调用 resolve
或 reject
回调函数进行处理。示例代码如下:
// 1.方法的名称为 getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
// 3.方法的返回值是 Promise 的实例对象
//resolve 形参是:调用getFile() 方法时,通过 .then 指定的 "成功的" 回调函数
//reject 形参是:调用getFile() 方法时,通过 .then 指定的 "失败的" 回调函数
return new Promise(function (resolve, reject) {
// 4.下面这行代码,表示这是一个具体的、读取文件的异步操作
fs.readFile(fpath, 'utf8', (err, dataStr) => {
if (err) return reject(err) //如果读取失败,则调用"失败的回调函数"
resolve(dataStr) //如果读取成功,则调用"成功的回调函数"
})
})
}
// getFile 方法的调用过程
getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)
import fs from 'fs'
// 1.方法的名称为 getFile
// 2.方法接收一个形参 fpath,表示要读取的文件的路径
function getFile(fpath) {
// 3.方法的返回值是 Promise 的实例对象
//resolve 形参是:调用getFile() 方法时,通过 .then 指定的 "成功的" 回调函数
//reject 形参是:调用getFile() 方法时,通过 .then 指定的 "失败的" 回调函数
return new Promise(function (resolve, reject) {
// 4.下面这行代码,表示这是一个具体的、读取文件的异步操作
fs.readFile(fpath, 'utf8', (err, dataStr) => {
if (err) return reject(err) //如果读取失败,则调用"失败的回调函数"
resolve(dataStr) //如果读取成功,则调用"成功的回调函数"
})
})
}
// getFile 方法的调用过程
// getFile('./files/1.txt').then(成功的回调函数,失败的回调函数)
getFile('./files/1.txt').then(r => { console.log(r); }, err => { console.log(err.message); })
getFile('./files/12.txt').then(r => { console.log(r); }, err => { console.log(err.message); })