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