JS中或node中,都大量的使用了回调函数进行异步操作,而异步操作什么时候返回结果是不可控的,如果我们希望几个异步请求按照顺序来执行,那么就需要将这些异步操作嵌套起来,嵌套的层数特别多,就会形成回调地狱?或者叫做?横向金字塔。
下面的案例就有回调地狱的意思:
案例:有 a.txt、b.txt、c.txt 三个文件,使用fs模板按照顺序来读取里面的内容,代码:
// 将读取的a、b、c里面的内容,按照顺序输出
const fs = require('fs');
// 读取a文件
fs.readFile('./a.txt', 'utf-8', (err, data) => {
if (err) throw err;
console.log(data.length);
// 读取b文件
fs.readFile('./b.txt', 'utf-8', (err, data) => {
if (err) throw err;
console.log(data);
// 读取c文件
fs.readFile('./c.txt', 'utf-8', (err, data) => {
if (err) throw err;
console.log(data);
});
});
});
案例中,只有三个文件,试想如果需要按照顺序读取的文件非常多,那么嵌套的代码将会多的可怕,这就是回调地狱的意思。
Promise可以理解为一个容器,里面可以编写异步程序的代码
new
Promise是“承诺”的意思,实例中,它里面的异步操作就相当于一个承诺,而承诺就会有两种结果,要么完成了承诺的内容,要么失败。
所以,使用Promise,分为两大部分,首先是有一个承诺(异步操作),然后再兑现结果。
第一部分:定义“承诺”
// 实例化一个Promise,表示定义一个容器,需要给它传递一个函数作为参数,而该函数又有两个形参,通常用resolve和reject来表示。该函数里面可以写异步请求的代码
// 换个角度,也可以理解为定下了一个承诺
let p = new Promise((resolve, reject) => {
// 形参resolve,单词意思是 完成
// 形参reject ,单词意思是 失败
fs.readFile('./a.txt', 'utf-8', (err, data) => {
if (err) {
// 失败,就告诉别人,承诺失败了
reject(err);
} else {
// 成功,就告诉别人,承诺实现了
resolve(data.length);
}
});
});
第二部分:获取“承诺”的结果
// 通过调用 p 的then方法,可以获取到上述 “承诺” 的结果
// then方法有两个函数类型的参数,参数1表示承诺成功时调用的函数,参数2可选,表示承诺失败时执行的函数
p.then(
(data) => {},
(err) => {}
);
完整的代码:
const fs = require('fs');
// promise 承诺
// 使用Promise分为两大部分
// 1. 定义一个承诺
let p = new Promise((resolve, reject) => {
// resolve -- 解决,完成了; 是一个函数
// reject -- 拒绝,失败了; 是一个函数
// 异步操作的代码,它就是一个承诺
fs.readFile('./a.txt', 'utf-8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.length);
}
});
});
// 2. 兑现承诺
// p.then(
// (data) => {}, // 函数类似的参数,用于获取承诺成功后的数据
// (err) => {} // 函数类型的参数,用于或承诺失败后的错误信息
// );
p.then(
(data) => {
console.log(data);
},
(err) => {
console.log(err);
}
);
当达到最终的 fulfilled 或 rejected 时,promise的状态就不会再改变了。
当调用 resolve的时候,Promise 将到达最终的状态。 达到最终状态之后,Promise的状态就不会再改变了。
多次调用 resolve 函数,只有第一次有效,其他的调用都无效。
const fs = require('fs');
// 1. 创建 Promise 对象;(承诺)
let p = new Promise((resolve, reject) => {
resolve(123);
resolve(456); // 这次调用无效
});
// 2. 获取异步任务的结果
// p.then(函数1, [函数2]);
p.then(res => {
console.log(res); // 123
}, err => {
console.log(err);
});
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
console.log(3);
}).then(res => {
console.log(4); // 这里是异步的
})
console.log(5);
// 输出顺序: 1 2 3 5 4 ,因为只有 .then() 是异步的
前一个then里面返回的字符串,会被下一个then方法接收到。但是没有意义;
前一个then里面返回的Promise对象,并且调用resolve的时候传递了数据,数据会被下一个then接收到
前一个then里面如果没有调用resolve,则后续的then不会接收到任何值
const fs = require('fs');
// promise 承诺
let p1 = new Promise((resolve, reject) => {
fs.readFile('./a.txt', 'utf-8', (err, data) => {
err ? reject(err) : resolve(data.length);
});
});
let p2 = new Promise((resolve, reject) => {
fs.readFile('./b.txt', 'utf-8', (err, data) => {
err ? reject(err) : resolve(data.length);
});
});
let p3 = new Promise((resolve, reject) => {
fs.readFile('./c.txt', 'utf-8', (err, data) => {
err ? reject(err) : resolve(data.length);
});
});
p1.then(a => {
console.log(a);
return p2;
}).then(b => {
console.log(b);
return p3;
}).then(c => {
console.log(c)
}).catch((err) => {
console.log(err);
});
function myReadFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
err ? reject(err) : resolve(data.length);
})
});
}
myReadFile('./a.txt')
.then(a => {
console.log(a);
return myReadFile('./b.txt');
})
.then(b => {
console.log(b);
return myReadFile('./c.txt');
})
.then(c => {
console.log(c)
})
.catch((err) => {
console.log(err);
});
// then-fs 模块是第三方模块,需要 npm install then-fs 下载安装的
const fs = require('then-fs');
// then-fs 对内置的fs模块进行了重写的封装。调用方法后,返回Promise对象
let p1 = fs.readFile('./files/a.txt', 'utf-8'); //
let p2 = fs.readFile('./files/b.txt', 'utf-8');
let p3 = fs.readFile('./files/c.txt', 'utf-8');
// 通过then获取结果
p1.then(res => {
console.log(res.length);
return p2;
}).then(res => {
console.log(res.length);
return p3;
}).then(res => {
console.log(res.length);
})
async 和 await 是 ES2017 中提出来的。
异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。
从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。
异步I/O不就是读取一个文件吗,干嘛要搞得这么复杂?异步编程的最高境界,就是根本不用关心它是不是异步。
==async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案==。
ES2017 提供了async和await关键字。await和async关键词能够将异步请求的结果以返回值的方式返回给我们。
.catch()
?或 在promise对象后使用?.catch()
const fs = require('fs');
// 将异步读取文件的代码封装
function myReadFile (path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
err ? reject(err) : resolve(data.length);
});
}).catch(err => {
console.log(err);
});
}
async function abc () {
let a = await myReadFile('./a.txt');
let b = await myReadFile('./b.txt');
let c = await myReadFile('./c.txt');
console.log(b);
console.log(a);
console.log(c);
}
abc();
前提是得到几个Promise对象,代码如下:
let fs = require('then-fs');
let p1 = fs.readFile('./files/a.txt', 'utf-8');
let p2 = fs.readFile('./files/bbb.txt', 'utf-8'); // 注意,这里故意写错路径
let p3 = fs.readFile('./files/c.txt', 'utf-8');
获取Promise的结果,可以通过 then 来获取。也可以通过 async 和 await 获取。
如果使用 then 获取结果,那么错误如何处理:在链式调用的尾端,加一个catch方法即可
// ---------------------- 通过 then 获取结果 ----------------------------
p1.then(res => {
console.log(res.length);
return p2;
}).then(res => {
console.log(res.length);
return p3;
}).then(res => {
console.log(res.length);
}).catch(err => {
console.log(err);
})
如果使用async和await获取结果,最好的错误处理方案,就是使用?try ... catch ...
// ---------------------- 通过 async/await 获取结果 ----------------------------
async function abc() {
try { // 尝试做一些事情
let r1 = await p1; // 正常得到结果
let r2 = await p2; // 这里出错了,就会抛出错误 throw err。
let r3 = await p3;
console.log(r1.length, r2.length, r3.length);
} catch (e) {
console.log(e); // catch这里,会抓住前面try里面抛出的错误
}
}
abc();