diff --git a/06-JavaScript异步编程:Ajax和Promise/01-单线程和异步.md b/06-JavaScript异步编程:Ajax和Promise/01-单线程和异步.md
index 4cd036c..ddba2db 100644
--- a/06-JavaScript异步编程:Ajax和Promise/01-单线程和异步.md
+++ b/06-JavaScript异步编程:Ajax和Promise/01-单线程和异步.md
@@ -24,11 +24,13 @@ JavaScript 语言和执行环境是**单线程**。即同一时间,只能处
## 同步任务和异步任务
-如果当前正在执行的任务很耗时,它就会**阻塞**其他正在排队的任务。为了解决这个问题,JS 在设计之初,将任务分成了两类:同步任务、异步任务。
+### 定义
+
+如果当前正在执行的任务执行完成前,它就会**阻塞**其他正在排队的任务。为了解决这个问题,JS 在设计之初,将任务分成了两类:同步任务、异步任务。
- 同步任务:在**主线程**上排队执行的任务。只有前一个任务执行完毕,才能执行下一个任务。
-- 异步任务:不进入主线程、而是进入**任务队列**(Event Queue)的任务。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
+- 异步任务:不进入主线程、而是进入**任务队列**(Event Queue)的任务,该任务不会阻塞后面的任务执行。只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
代码举例:
@@ -54,17 +56,16 @@ console.log('同步任务2');
比如说,网络图片的请求,就是一个异步任务。前端如果同时请求多张网络网络图片,谁先请求完成就让谁先显示出来。假如网络图片的请求做成同步任务,那就会出大问题,所有图片都得排队加载,如果第一张图片未加载完成,就得卡在那里,造成阻塞,导致其他图片都加载不出来。页面看上去也会很卡顿,这肯定是不能接受的。
-### 前端使用异步的场景
+### 前端使用异步编程的场景
什么时候需要**等待**,就什么时候用异步。常见的异步场景如下:
-- 1、定时器:setTimeout(定时炸弹)、setInterval(循环执行)
-
-- 2、事件绑定(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步)
-
-- 3、网络请求(含接口请求):ajax 请求、网络图片加载
-
-- 4、ES6 中的 Promise
+- 1、事件监听(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步)
+- 2、回调函数:
+ - 2.1、定时器:setTimeout(定时炸弹)、setInterval(循环执行)
+ - 2.2、ajax请求。
+ - 2.3、Node.js 中的一些方法回调。
+- 3、ES6 中的 Promise、Generator、async/await
现在的大部分软件项目,都是前后端分离的。后端生成接口,前端请求接口。前端发送 ajax 请求,向后端请求数据,然后**等待一段时间**后,才能拿到数据。这个请求过程就是异步任务。
diff --git a/06-JavaScript异步编程:Ajax和Promise/05-Promise入门详解.md b/06-JavaScript异步编程:Ajax和Promise/05-Promise入门详解.md
index dc966b4..cf36479 100644
--- a/06-JavaScript异步编程:Ajax和Promise/05-Promise入门详解.md
+++ b/06-JavaScript异步编程:Ajax和Promise/05-Promise入门详解.md
@@ -5,7 +5,6 @@ publish: true
-
## 为什么需要 Promise?
我们在前面的文章《JavaScript 基础:异步编程和 Ajax/单线程和异步》中讲过,Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时,⼤部分情况都是通过回调函数来进⾏。
@@ -38,9 +37,9 @@ dynamicFunc(function () {
回调的写法比较直观,不需要 return,层层嵌套即可。但也存在两个问题:
-- 1、如果嵌套过深,则会出现**回调地狱**的问题。
+- 1. 如果嵌套过深,则会出现**回调地狱**的问题。
-- 2、不同的函数,回调的参数,在写法上可能不一致,导致不规范、且需要**单独记忆**。
+- 2. 不同的函数,回调的参数,在写法上可能不一致,导致不规范、且需要**单独记忆**。
我们来具体看看这两个问题。
@@ -54,9 +53,9 @@ dynamicFunc(function () {
> 但真实的场景中,实际的操作流程是:买菜成功之后,才能开始做饭。做饭成功后,才能开始洗碗。洗碗完成后, 再倒厨余垃圾。这里的一系列动作就涉及到了多层嵌套调用,也就是回调地狱。
-关于回调地狱,我们来看看两段代码。
+关于回调地狱,我们来看看几段代码举例。
-定时器的代码举例:(回调地狱)
+1.1、定时器的代码举例:(回调地狱)
```js
setTimeout(function () {
@@ -70,7 +69,23 @@ setTimeout(function () {
}, 1000);
```
-ajax 请求的代码举例:(回调地狱)
+1.2、Node.js 读取文件的代码举例:(回调地狱)
+
+```js
+fs.readFile(A, 'utf-8', function (err, data) {
+ fs.readFile(B, 'utf-8', function (err, data) {
+ fs.readFile(C, 'utf-8', function (err, data) {
+ fs.readFile(D, 'utf-8', function (err, data) {
+ console.log('qianguyihao:' + data);
+ });
+ });
+ });
+});
+```
+
+上面代码的逻辑为:先读取 A 文本内容,再根据 A 文本内容读取 B,然后再根据 B 的内容读取 C。为了实现这个业务逻辑,上面的代码就很容易形成回调地狱。
+
+1.3、ajax 请求的代码举例:(回调地狱)
```js
// 伪代码
@@ -89,7 +104,7 @@ ajax('a.json', (res1) => {
```js
// Node.js 读取文件时,成功回调和失败回调,是通过 error参数来区分
-readFile('d:\\readme.text', function (error, data) {
+readFile('d:\\readme.text', function (err, data) {
if (error) {
console.log('文件读取失败');
} else {
@@ -113,9 +128,9 @@ $.ajax({
**小结**:
-在 ES5 中,当进行多层嵌套回调时,会导致代码层次过多,很难进行后续维护和二次开发;而且会导致**回调地狱**的问题。ES6 中的 Promise 就可以解决这两个问题。
+在 ES5 中,当进行多层嵌套回调时,会导致代码层次过多,很难进行后续维护和二次开发;而且会导致**回调地狱**的问题。ES6 中的 Promise 就可以解决这些问题。
-当然, Promise 的更强大功能,不止于此。我们来一探究竟。
+当然, Promise 的强大功能,不止于此。我们来一探究竟。
### Promise 的介绍和优点
@@ -227,7 +242,7 @@ promise.then(
`new Promise()`这行代码本身是同步的。promise 如果没有使用 resolve 或 reject 更改状态时,状态为 pending。
-**举例1**:
+**举例 1**:
```js
const promiseA = new Promise((resolve, reject) => {});
@@ -238,7 +253,7 @@ console.log(promiseA); // 此时 promise 的状态为 pending(准备阶段)
当完成异步任务之后,状态分为成功或失败,此时我们就可以用 reslove() 和 reject() 来修改 promise 的状态。
-**举例2**:
+**举例 2**:
```js
new Promise((resolve, reject) => {
@@ -254,20 +269,19 @@ new Promise((resolve, reject) => {
promise1
```
-
上方代码,仔细看注释:如果前面没有写 `resolve()`,那么后面的 `.then`是不会执行的。
-**举例3**:
+**举例 3**:
```js
new Promise((resolve, reject) => {
resolve();
- console.log('promise1'); // 代码1:同步任务,会立即执行
+ console.log('promise1'); // 代码1:同步任务,会立即执行
}).then(res => {
- console.log('promise then'); // 代码2:异步任务中的微任务
-})
+ console.log('promise then'); // 代码2:异步任务中的微任务
+});
-console.log('千古壹号'); // 代码3:同步任务
+console.log('千古壹号'); // 代码3:同步任务
```
打印结果:
@@ -278,11 +292,10 @@ promise1
promise then
```
-代码解释:代码1是同步代码,所以最先执行。代码2是**微任务**里面的代码,所以要先等同步任务(代码3)先执行完。当写完`resolve();`之后,就会立刻把 `.then()`里面的代码加入到微任务队列当中。
+代码解释:代码 1 是同步代码,所以最先执行。代码 2 是**微任务**里面的代码,所以要先等同步任务(代码 3)先执行完。当写完`resolve();`之后,就会立刻把 `.then()`里面的代码加入到微任务队列当中。
补充知识:异步任务分为“宏任务”、“微任务”两种。我们到后续的章节中再详细讲。
-
### Promise 的状态一旦改变,就不能再变
代码举例:
@@ -324,7 +337,7 @@ Promise 是⼀个拥有 then ⽅法的对象或函数。任何符合 promise 规
了解这些常见概念之后,接下来,我们来具体看看 promise 的代码是怎么写的。
-## Promise 封装异步任务
+## Promise 封装定时器
### 传统写法
@@ -403,9 +416,9 @@ function ajax(url, success, fail) {
xmlhttp.send();
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
- success && success(xmlhttp.responseText);
+ success && success(xmlhttp.responseText);
} else {
- // 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。
+ // 这里的 && 符号,意思是:如果传了 fail 参数,就调用后面的 fail();如果没传 fail 参数,就不调用后面的内容。因为 fail 参数不一定会传。
fail && fail(new Error('接口请求失败'));
}
};
diff --git a/06-JavaScript异步编程:Ajax和Promise/06-Promise的链式调用.md b/06-JavaScript异步编程:Ajax和Promise/06-Promise的链式调用.md
index 69e3980..d0a5531 100644
--- a/06-JavaScript异步编程:Ajax和Promise/06-Promise的链式调用.md
+++ b/06-JavaScript异步编程:Ajax和Promise/06-Promise的链式调用.md
@@ -5,7 +5,6 @@ publish: true
-
## Promise 的链式调用:处理多次 Ajax 请求【重要】
实际开发中,我们经常需要同时请求多个接口。比如说:在请求完`接口1`的数据`data1`之后,需要根据`data1`的数据,继续请求接口 2,获取`data2`;然后根据`data2`的数据,继续请求接口 3。
@@ -168,7 +167,7 @@ getPromise('a.json')
代码写到这里,我们还可以再继续优化一下。细心的你可以发现,我们在做三次嵌套请求的时候,针对 resolve 和 reject 的处理时机是一样的。如果你的业务是针对**同一个接口**连续做了三次调用,只是请求**传参不同**,那么,按上面这样写是没有问题的。
-但是,真正在实战中,我们往往需要嵌套请求**多个不同的接口**,要处理的 resolve 和 reject 的时机往往是不同的,所以需要分开封装不同的 Promise 实例,这在实战开发中更为常见。代码应该是像下面这样写。
+但是,真正在实战中,我们往往需要嵌套请求**多个不同的接口**,要处理的 resolve 和 reject 的时机和逻辑往往是不同的,所以需要分开封装不同的 Promise 实例,这在实战开发中更为常见。代码应该是像下面这样写。
### Promise 链式调用(封装多个接口)
@@ -256,6 +255,56 @@ request1()
这段代码很经典,你一定要多看几遍,多默写几遍。倒背如流也不过分。
+## Promise 链式调用:封装 Node.js 的回调方法
+
+### 传统写法
+
+```js
+fs.readFile(A, 'utf-8', function (err, data) {
+ fs.readFile(B, 'utf-8', function (err, data) {
+ fs.readFile(C, 'utf-8', function (err, data) {
+ fs.readFile(D, 'utf-8', function (err, data) {
+ console.log('qianguyihao:' + data);
+ });
+ });
+ });
+});
+```
+
+上方代码多层嵌套,存在回调地狱的问题。
+
+### Promise 写法
+
+```js
+function read(url) {
+ return new Promise((resolve, reject) => {
+ fs.readFile(url, 'utf8', (err, data) => {
+ if (err) reject(err);
+ resolve(data);
+ });
+ });
+}
+
+read(A)
+ .then((data) => {
+ return read(B);
+ })
+ .then((data) => {
+ return read(C);
+ })
+ .then((data) => {
+ return read(D);
+ })
+ .then((data) => {
+ console.log('qianguyihao:' + data);
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+```
+
+这一段代码可以看出,Promise 很好的处理了回调地狱的问题。下一篇文章,我们会更详细的介绍 Promise 的链式调用。
+
## 链式调用,如何处理 reject 失败状态
### 例 1:不处理 reject