diff --git a/06-JavaScript基础:ES6语法/10-Promise入门详解.md b/06-JavaScript基础:ES6语法/10-Promise入门详解.md index 2b8c51b..33e769d 100644 --- a/06-JavaScript基础:ES6语法/10-Promise入门详解.md +++ b/06-JavaScript基础:ES6语法/10-Promise入门详解.md @@ -1,6 +1,6 @@ ## 为什么需要 Promise? -我们在上一篇文章《单线程和异步》中讲过,Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时,⼤部分情况都是通过回调函数来进⾏。 +我们在前面的文章《JavaScript基础:异步编程和Ajax/单线程和异步》中讲过,Javascript 是⼀⻔单线程语⾔。早期我们解决异步场景时,⼤部分情况都是通过回调函数来进⾏。 (如果你还不了解单线程和异步的概念,可以先去回顾上一篇文章。) @@ -430,7 +430,7 @@ promiseB 注意,如果你用的是写法 1(将 promise 实例定义为函数),则调用 promise 的时候是`promiseA().then()`,如果你用的是写法 2(将 promise 实例定位为函数),则调用的时候用的是`promiseB.then()`。写法 1 多了个括号,不要搞混了。 -### 捕获 reject 异常状态的两种写法 +## 捕获 reject 异常状态的两种写法 我们有两种写法可以捕获并处理 reject 异常状态。这两种写法的代码举例如下: @@ -482,265 +482,7 @@ try-catch 主要用于捕获异常,注意,这里的异常是指**同步**函 (2)写法 1 中,`promiseA().then().catch()`和`promiseA().catch().then()`区别在于:前者可以捕获到 `then` 里面的异常,后者不可以。 -## 链式调用:基于 Promise 处理多次 Ajax 请求【重要】 -实际开发中,我们经常需要同时请求多个接口。比如说:在请求完`接口1`的数据`data1`之后,需要根据`data1`的数据,继续请求接口 2,获取`data2`;然后根据`data2`的数据,继续请求接口 3。 - -换而言之,现在有三个网络请求,请求 2 必须依赖请求 1 的结果,请求 3 必须依赖请求 2 的结果,如果按照往常的写法,会有三层回调,会陷入“回调地狱”。 - -这种场景其实就是接口的多层嵌套调用。有了 Promise 之后,我们可以把多层嵌套调用按照**线性**的方式进行书写,非常优雅。也就是说:Promise 可以把原本的**多层嵌套写法**改进为**链式写法**。 - -### 传统写法 - -```js -// 封装 ajax 请求:传入回调函数 success 和 fail -function ajax(url, success, fail) { - var xmlhttp = new XMLHttpRequest(); - xmlhttp.open('GET', url); - xmlhttp.send(); - xmlhttp.onreadystatechange = function () { - if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { - success(xmlhttp.responseText); - } else { - fail(new Error('接口请求失败')); - } - }; -} - -// 执行 ajax 请求 -ajax( - '/a.json', - (res) => { - console.log('qianguyihao 第一个接口请求成功:' + JSON.stringify(res)); - // ajax嵌套调用 - ajax('b.json', (res) => { - console.log('qianguyihao 第二个接口请求成功:' + JSON.stringify(res)); - // ajax嵌套调用 - ajax('c.json', (res) => { - console.log('qianguyihao 第三个接口请求成功:' + JSON.stringify(res)); - }); - }); - }, - (err) => { - console.log('qianguyihao 请求失败:' + JSON.stringify(err)); - } -); -``` - -上面的代码层层嵌套,出现了我们常说的回调地狱问题。 - -### Promise 链式调用(初步写法,方便理解) - -如果我们不对 Promise 的链式调用进行封装,那么,它的简单写法是下面这样的: - -```js -// 封装 ajax 请求:传入回调函数 success 和 fail -function ajax(url, success, fail) { - var xmlhttp = new XMLHttpRequest(); - xmlhttp.open('GET', url); - xmlhttp.send(); - xmlhttp.onreadystatechange = function () { - if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { - success(xmlhttp.responseText); - } else { - fail(new Error('接口请求失败')); - } - }; -} - -new Promise((resolve, reject) => { - ajax('a.json', (res) => { - console.log(res); - resolve(); - }); -}) - .then((res) => { - console.log('a成功'); - return new Promise((resolve, reject) => { - ajax('b.json', (res) => { - console.log(res); - resolve(); - }); - }); - }) - .then((res) => { - console.log('b成功'); - return new Promise((resolve, reject) => { - ajax('c.json', (res) => { - console.log(res); - resolve(); - }); - }); - }) - .then((res) => { - cnosole.log('c成功'); - }); -``` - -上面代码中,then 是可以链式调用的,一旦 return 一个新的 promise 实例之后,后面的 then 就可以拿到前面 resolve 出来的数据。这种**扁平化**的写法,更方便维护;并且可以更好的**管理**请求成功和失败的状态。 - -你可能会奇怪,上面的代码,怎么这么多?而且有不少重复。这里只是采用了一种笨拙的方式来写,为的是方便大家理解 promise 的执行过程。我们其实可以对 promise 的链式调用进行进一步封装。 - -怎么个封装法呢?上面的代码中,每次在 return 一个 promise 的时候,只是 url 地址不一样,其他的代码是一样的。所以我们可以把重复的代码封装成函数。 - -### Promise 链式调用(封装写法) - -封装 Ajax 请求的链式调用,代码举例: - -```js -// 定义 ajax 请求:传入回调函数 success 和 fail -function ajax(url, success, fail) { - var xmlhttp = new XMLHttpRequest(); - xmlhttp.open('GET', url); - xmlhttp.send(); - xmlhttp.onreadystatechange = function () { - if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { - success(xmlhttp.responseText); - } else { - fail(new Error('接口请求失败')); - } - }; -} - -// 第一步:model层,接口封装 -function getPromise(url) { - return new Promise((resolve, reject) => { - ajax(url, (res) => { - // 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。 - if (res.retCode == 0) { - // 接口请求成功时调用 - resolve('request success' + res); - } else { - // 接口请求异常时调用 - reject({ retCode: -1, msg: 'network error' }); - } - }); - }); -} - -// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据 -getPromise('a.json') - .then((res) => { - // a 请求成功。从 resolve 获取正常结果:接口请求成功后,打印a接口的返回结果 - console.log(res); - return getPromise('b.json'); // 继续请求 b - }) - .then((res) => { - // b 请求成功 - console.log(res); - return getPromise('c.json'); // 继续请求 c - }) - .then((res) => { - // c 请求成功 - console.log(res); - }) - .catch((e) => { - // 从 reject中获取异常结果 - console.log(e); - }); -``` - -怎么样?上面代码中,是不是非常简洁?而且可读性很强。 - -代码写到这里,我们还可以再继续优化一下。细心的你可以发现,我们在依次请求三个接口的时候,里面针对 resolve 和 reject 的处理时机是一样的。 - -但是,真正在实战中,我们在调不用的接口时,要处理的 resolve 和 reject 的时机往往是不同的。所以分开封装 不同的 Promise 实例。实战中的代码,应该是像下面这样写。 - -### Promise 链式调用(封装写法,实战版) - -```html - - - - - - Document - - - - - -``` - -这段代码很经典,你一定要多看几遍。 ## return 的函数返回值 diff --git a/06-JavaScript基础:ES6语法/11-Promise的链式调用.md b/06-JavaScript基础:ES6语法/11-Promise的链式调用.md new file mode 100644 index 0000000..9f66b9a --- /dev/null +++ b/06-JavaScript基础:ES6语法/11-Promise的链式调用.md @@ -0,0 +1,251 @@ +## Promise 的链式调用:处理多次 Ajax 请求【重要】 + +实际开发中,我们经常需要同时请求多个接口。比如说:在请求完`接口1`的数据`data1`之后,需要根据`data1`的数据,继续请求接口 2,获取`data2`;然后根据`data2`的数据,继续请求接口 3。 + +换而言之,现在有三个网络请求,请求 2 必须依赖请求 1 的结果,请求 3 必须依赖请求 2 的结果,如果按照往常的写法,会有三层回调,会陷入“回调地狱”。 + +这种场景其实就是接口的多层嵌套调用。有了 Promise 之后,我们可以把多层嵌套调用按照**线性**的方式进行书写,非常优雅。也就是说:Promise 可以把原本的**多层嵌套写法**改进为**链式写法**。 + +### ES5 中的传统写法 + +```js +// 封装 ajax 请求:传入回调函数 success 和 fail +function ajax(url, success, fail) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET', url); + xmlhttp.send(); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { + success(xmlhttp.responseText); + } else { + fail(new Error('接口请求失败')); + } + }; +} + +// 执行 ajax 请求 +ajax( + '/a.json', + (res) => { + console.log('qianguyihao 第一个接口请求成功:' + JSON.stringify(res)); + // ajax嵌套调用 + ajax('b.json', (res) => { + console.log('qianguyihao 第二个接口请求成功:' + JSON.stringify(res)); + // ajax嵌套调用 + ajax('c.json', (res) => { + console.log('qianguyihao 第三个接口请求成功:' + JSON.stringify(res)); + }); + }); + }, + (err) => { + console.log('qianguyihao 请求失败:' + JSON.stringify(err)); + } +); +``` + +上面的代码层层嵌套,可读性很差,而且出现了我们常说的回调地狱问题。 + +### Promise 链式调用(初步写法,方便理解) + +如果我们不对 Promise 的链式调用进行封装,那么,它的简单写法是下面这样的: + +```js +// 封装 ajax 请求:传入回调函数 success 和 fail +function ajax(url, success, fail) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET', url); + xmlhttp.send(); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { + success(xmlhttp.responseText); + } else { + fail(new Error('接口请求失败')); + } + }; +} + +new Promise((resolve, reject) => { + ajax('a.json', (res) => { + console.log(res); + resolve(); + }); +}) + .then((res) => { + console.log('a成功'); + return new Promise((resolve, reject) => { + ajax('b.json', (res) => { + console.log(res); + resolve(); + }); + }); + }) + .then((res) => { + console.log('b成功'); + return new Promise((resolve, reject) => { + ajax('c.json', (res) => { + console.log(res); + resolve(); + }); + }); + }) + .then((res) => { + cnosole.log('c成功'); + }); +``` + +上面代码中,then 是可以链式调用的,一旦 return 一个新的 promise 实例之后,后面的 then 就可以拿到前面 resolve 出来的数据。这种**扁平化**的写法,更方便维护;并且可以更好的**管理**请求成功和失败的状态。 + +但是,你可能会奇怪,上面的代码,怎么这么多?而且有不少重复。这里只是采用了一种笨拙的方式来写,为的是方便大家理解 promise 的执行过程。我们其实可以对 promise 的链式调用进行进一步封装。 + +怎么个封装法呢?上面的代码中,每次在 return 一个 promise 的时候,只是 url 地址不一样,其他的代码是一样的。所以我们可以把重复的代码封装成函数。 + +### Promise 链式调用(封装写法) + +针对同一个接口的多次嵌套调用,采用 promise 封装后的写法如下: + +```js +// 定义 ajax 请求:传入回调函数 success 和 fail +function ajax(url, success, fail) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET', url); + xmlhttp.send(); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { + success(xmlhttp.responseText); + } else { + fail(new Error('接口请求失败')); + } + }; +} + +// 第一步:model层,接口封装 +function getPromise(url) { + return new Promise((resolve, reject) => { + ajax(url, (res) => { + // 这里的 res 是接口的返回结果。返回码 retCode 是动态数据。 + if (res.retCode == 0) { + // 接口请求成功时调用 + resolve('request success' + res); + } else { + // 接口请求异常时调用 + reject({ retCode: -1, msg: 'network error' }); + } + }); + }); +} + +// 第二步:业务层的接口调用。这里的 data 就是 从 resolve 和 reject 传过来的,也就是从接口拿到的数据 +getPromise('a.json') + .then((res) => { + // a 请求成功。从 resolve 获取正常结果:接口请求成功后,打印a接口的返回结果 + console.log(res); + return getPromise('b.json'); // 继续请求 b + }) + .then((res) => { + // b 请求成功 + console.log(res); + return getPromise('c.json'); // 继续请求 c + }) + .then((res) => { + // c 请求成功 + console.log(res); + }) + .catch((e) => { + // 从 reject中获取异常结果 + console.log(e); + }); +``` + +怎么样?上面代码中,是不是非常简洁?而且可读性很强。 + +代码写到这里,我们还可以再继续优化一下。细心的你可以发现,我们在依次请求三个接口的时候,里面针对 resolve 和 reject 的处理时机是一样的。 + +但是,真正在实战中,我们在调不用的接口时,要处理的 resolve 和 reject 的时机往往是不同的。所以分开封装 不同的 Promise 实例。实战中的代码,应该是像下面这样写。 + +### Promise 链式调用(封装写法,多个接口) + +针对多个不同接口的嵌套调用,采用 promise 封装后的写法如下: + +```js +// 封装 ajax 请求:传入回调函数 success 和 fail +function ajax(url, success, fail) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET', url); + xmlhttp.send(); + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { + success(xmlhttp.responseText); + } else { + fail(new Error('接口请求失败')); + } + }; +} + +// Promise 封装接口1 +function request1() { + return new Promise((resolve, reject) => { + ajax('https://www.baidu.com', (res) => { + if (res.retCode == 201) { + // 接口请求成功时调用:这里的 res 是接口1的返回结果 + resolve('request1 success' + res); + } else { + // 接口请求异常时调用异常 + reject('接口1请求失败'); + } + }); + }); +} + +// Promise 封装接口2 +function request2() { + return new Promise((resolve, reject) => { + ajax('https://www.jd.com', (res) => { + if (res.retCode == 202) { + // 这里的 res 是接口2的返回结果 + resolve('request2 success' + res); + } else { + reject('接口2请求失败'); + } + }); + }); +} + +// Promise 封装接口3 +function request3() { + return new Promise((resolve, reject) => { + ajax('https://www.taobao.com', (res) => { + if (res.retCode == 203) { + // 这里的 res 是接口3的返回结果 + resolve('request3 success' + res); + } else { + reject('接口3请求失败'); + } + }); + }); +} + +// 先发起request1,等resolve后再发起request2;紧接着,等 request2有了 resolve之后,再发起 request3 +request1() + .then((res1) => { + // 接口1请求成功 + console.log(res1); + return request2(); + }) + .then((res2) => { + // 接口2请求成功 + console.log(res2); + return request3(); + }) + .then((res3) => { + // 接口3请求成功 + console.log(res3); + }) + .catch((err) => { + // 从 reject中获取异常结果 + console.log(err); + }); +``` + +这段代码很经典,你一定要多看几遍,多默写几遍。倒背如流也不过分。 + + diff --git a/06-JavaScript基础:ES6语法/11-Promise的常见用法和API.md b/06-JavaScript基础:ES6语法/12-Promise的常见用法和API.md similarity index 100% rename from 06-JavaScript基础:ES6语法/11-Promise的常见用法和API.md rename to 06-JavaScript基础:ES6语法/12-Promise的常见用法和API.md diff --git a/06-JavaScript基础:ES6语法/12-ES7:async函数详解.md b/06-JavaScript基础:ES6语法/13-ES7:async函数详解.md similarity index 100% rename from 06-JavaScript基础:ES6语法/12-ES7:async函数详解.md rename to 06-JavaScript基础:ES6语法/13-ES7:async函数详解.md