- 回调地狱
概念:多层回调函数的相互嵌套,就形成了回调地狱
代码如下:
setTimeout(() => { // 第一层回调函数
console.log( "延迟 1s 钟后执行" )
setTimeout(() => { // 第二层回调函数
console.log( "延迟 2s 钟后执行" )
setTimeout(() => { //第三层回调函数
console.log( "延迟 3s 钟后执行" )
}, 3000);
}, 2000);
}, 1000);
回调地狱的缺点:
- 代码耦合性太高,牵一发动全身,难以维护;
- 代码冗余,大量代码相互嵌套,难以阅读
1.1 如何解决回调地狱
为了解决回调地狱,ES6 新增了 Promise 的概念
1.2 promise 的基本概念
-
Promise 是一个构造函数:
- 所以它可以被 new 出来:
let p = new Promise() - new 出来的 Promise 示例对象 p 代表一个异步操作。
- 所以它可以被 new 出来:
-
Promise.propotype 上包含一个 .then 方法
- 每一次 new Promise() 得到的实例对象都可以通过原型链的方式访问到 .then() 方法,如 p.then()
-
.then() 方法用来预先指定成功和失败的回调函数
- p.then( 成功的回调函数, 失败的回调函数 )
- 写法为:
p.then( result =>{} , error=>{} ) - 调用 .then() 方法时,成功的回调函数时必选的,失败的回调是可选择的。
1.3 then-fs 的基本使用
调用 then-fs 提供的 readFile()方法可以异步的读取文件内容。他的返回值是 Promise 的实例对象。因此可以调用 .then() 方法为每个成功和失败的回调指定回调函数。
import thenFS from "then-fs"
thenFS.readFile("./1.txt", 'utf8').then((r1) => {console.log(r1)})
thenFS.readFile("./2.txt", 'utf8').then((r2) => {console.log(r2)})
thenFS.readFile("./3.txt", 'utf8').then((r3) => {console.log(r3)})
// 注意:上述代码无法保证文件的读取顺序。为了保证执行顺序,即先执行 1.txt -> 2.txt -> 3.txt 需要把代码按下面的方式写
1.4 .then() 方法的特性
如何遇到上一个 .then() 方法中返回了一个新的 Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用,就解决了了回调地狱问题。
//下面代码是按照顺序执行的:1.txt -> 2.txt -> 3.txt
thenFS.readFile("./1.txt", "utf8") // 1. 返回值是 promise 的实例对象
.then( r1 =>{ // 2. 通过 .then 为第一个返回值指定成功的回调函数
console.log(r1)
return thenFS.readFile("./2.txt", "utf8") // 3. 在第一个 .then中返回一个新的 Promise实例对象,这样后面可以继续使用 .then
})
.then((r2) => { // 4. 继续调用 .then() 方法,为上一个返回值(Promise 实例对象)指定成功的回调
console.log(r2);
return thenFS.readFile("./3.txt", "utf8") // 5. 再返回一个回调函数出来,这样后面就可以用 .then 方法
})
.then((r3) => { // 6. 继续调用 .then() 方法,为上一个返回值(Promise 实例对象)指定成功的回调
console.log(r3)
})
1.5 .catch() 捕获错误
在 Promise 的链式操作中如果发生了错误,我们可以用 Promise.propertype.catch() 方法捕获错误并进行处理,比如:
thenFS.readFile("./11.txt", "utf8") // 没有 11.txt 这个文件
.then( r1 =>{
console.log(r1)
return thenFS.readFile("./2.txt", "utf8")
})
.then((r2) => {
console.log(r2);
return thenFS.readFile("./3.txt", "utf8")
})
.then((r3) => {
console.log(r3)
})
.catch( err => { // 因为没有 11.txt 这个文件,所以错误会被此捕获并输出
console.log(err)
})
//输出结果为
//[Error: ENOENT: no such file or directory, open 'C:\Users\Administrator\Desktop\es6\11.txt'] {
// errno: -4058,
// code: 'ENOENT',
// syscall: 'open',
// path: 'C:\\Users\\Administrator\\Desktop\\es6\\11.txt'
//}
如果不希望前面的错误导致后面的代码无法执行,我们可以把 .catch() 的调用提前,如下示例
thenFS.readFile("./11.txt", "utf8") // 没有 11.txt 这个文件
.catch( err => { // 因为没有 11.txt 这个文件,所以错误会被此捕获并输出。后面的代码依旧会执行
console.log(err)
})
.then( r1 =>{
console.log(r1)
return thenFS.readFile("./2.txt", "utf8")
})
.then((r2) => {
console.log(r2);
return thenFS.readFile("./3.txt", "utf8")
})
.then((r3) => {
console.log(r3)
})
// 输出:
//undefined
// 222
// 333