佛山有那几家做网站,微餐饮网站建设官网,已经有备案的公司网站 还能不能加网站,wordpress个人博客网站前言
实际开发中#xff0c;我们经常需要先后请求多个接口#xff1a;发送第一次网络请求后#xff0c;等待请求结果#xff1b;有结果后#xff0c;然后发送第二次网络请求#xff0c;等待请求结果#xff1b;有结果后#xff0c;然后发送第三次网络请求。以此类推。…前言
实际开发中我们经常需要先后请求多个接口发送第一次网络请求后等待请求结果有结果后然后发送第二次网络请求等待请求结果有结果后然后发送第三次网络请求。以此类推。
比如说在请求完接口 1 的数据data1之后需要根据data1的数据继续请求接口 2获取data2然后根据data2的数据继续请求接口 3。换而言之现在有三个网络请求请求 2 必须依赖请求 1 的结果请求 3 必须依赖请求 2 的结果。
如果按照往常的写法会有三层回调陷入“回调地狱”的麻烦。
这种场景其实就是接口的多层嵌套调用在前端的异步编程开发中经常遇到。有了 Promise 以及更高级的写法之后我们可以把多层嵌套调用按照线性的方式进行书写非常优雅。也就是说Promise 等ES6的写法可以把原本的多层嵌套写法改进为链式写法。
我们来对比一下嵌套写法和链式调用的写法你会发现后者的非常优雅。
Promise 链式调用封装多次网络请求
ES5 中的传统嵌套写法
伪代码举例 // 封装 ajax 请求传入请求地址、请求参数以及回调函数 success 和 fail。function requestAjax(url, params, success, fail) {var xhr new xhrRequest();// 设置请求方法、请求地址。请求地址的格式一般是https://api.example.com/data? key1value1key2value2xhr.open(GET, url);// 设置请求头如果需要xhr.setRequestHeader(Content-Type, application/json);xhr.send();xhr.onreadystatechange function () {if (xhr.readyState 4 xhr.status 200) {success success(xhr.responseText);} else {fail fail(new Error(接口请求失败));}};}// ES5的传统写法执行 ajax 请求层层嵌套requestAjax(https://api.qianguyihao.com/url_1, params_1,res1 {console.log(第一个接口请求成功: JSON.stringify(res1));// ajax嵌套调用requestAjax(https://api.qianguyihao.com/url_2, params_2, res2 {console.log(第二个接口请求成功: JSON.stringify(res2));// ajax嵌套调用requestAjax(https://api.qianguyihao.com/url_3, params_3, res3 {console.log(第三个接口请求成功: JSON.stringify(res3));});});},(err1) {console.log(qianguyihao 请求失败: JSON.stringify(err1));});
上面的代码层层嵌套可读性很差而且出现了我们常说的回调地狱问题。
Promise 的嵌套写法
改用 ES6 的 Promise 之后写法上会稍微改进一些。代码举例如下 // 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数以及回调函数 success 和 fail。function requestAjax(url, params, success, fail) {var xhr new xhrRequest();// 设置请求方法、请求地址。请求地址的格式一般是https://api.example.com/data? key1value1key2value2xhr.open(GET, url);// 设置请求头如果需要xhr.setRequestHeader(Content-Type, application/json);xhr.send();xhr.onreadystatechange function () {if (xhr.readyState 4 xhr.status 200) {success success(xhr.responseText);} else {fail fail(new Error(接口请求失败));}};}// 【model层】将接口请求封装为 Promisefunction requestData1(params_1) {return new Promise((resolve, reject) {requestAjax(https://api.qianguyihao.com/url_1, params_1, res {// 这里的 res 是接口返回的数据。返回码 retCode 为 0 代表接口请求成功。if (res.retCode 0) {// 接口请求成功时调用resolve(request success res);} else {// 接口请求异常时调用reject({ retCode: -1, msg: network error });}});});}// requestData2、requestData3的写法与 requestData1类似。他们的请求地址、请求参数、接口返回结果不同所以需要挨个单独封装 Promise。function requestData2(params_2) {return new Promise((resolve, reject) {requestAjax(https://api.qianguyihao.com/url_2, params_2, res {if (res.retCode 0) {resolve(request success res);} else {reject({ retCode: -1, msg: network error });}});});}function requestData3(params_3) {return new Promise((resolve, reject) {requestAjax(https://api.qianguyihao.com/url_3, params_3, res {if (res.retCode 0) {resolve(request success res);} else {reject({ retCode: -1, msg: network error });}});});}// 【业务层】Promise 调接口的嵌套写法。温馨提示这段代码在接下来的学习中会被改进无数次。// 发送第一次网络请求requestData1(params_1).then(res1 {console.log(第一个接口请求成功: JSON.stringify(res1));// 发送第二次网络请求requestData1(params_2).then(res2 {console.log(第二个接口请求成功: JSON.stringify(res2));// 发送第三次网络请求requestData1(params_3).then(res3 {console.log(第三个接口请求成功: JSON.stringify(res3));})})})
上方代码非常经典。在真正的实战中我们往往需要嵌套请求多个不同的接口它们的接口请求地址、要处理的 resolve 和 reject 的时机、业务逻辑往往是不同的所以需要分开封装不同的 Promise 实例。也就是说如果要调三个不同的接口建议单独封装三个不同的 Promise 实例requestData1、requestData2、requestData3。
这三个 Promise 实例最终都需要调用底层的公共方法 requestAjax()。每个公司都有这样的底层方法里面的代码会做一些公共逻辑比如封装原生的 ajax请求用户登录态的校验等等如果没有这种公共方法你就自己写一个为组织做点贡献。
但是细心的你可能会发现上面的最后10行代码仍然不够优雅因为 Promise 在调接口时出现了嵌套的情况实际开发中如果真这么写的话是比较挫的阅读性非常差我不建议这么写。要怎么改进呢这就需要用到 Promise 的链式调用。
Promise 的链式调用写法重要
针对多个不同接口的嵌套调用采用 Promise 的链式调用写法如下将上方代码的最后10行改进如下 requestData1(params_1).then(res1 {console.log(第一个接口请求成功: JSON.stringify(res1));// 【关键代码】继续请求第二个接口。如果有需要也可以把 res1 的数据传给 requestData2()的参数return requestData2(res1);}).then(res2 {console.log(第二个接口请求成功: JSON.stringify(res2));// 【关键代码】继续请求第三个接口。如果有需要也可以把 res2 的数据传给 requestData3()的参数return requestData3(res2);}).then(res3 {console.log(第三个接口请求成功: JSON.stringify(res3));}).catch(err {console.log(err);})
上面代码中then 是可以链式调用的一旦 return 一个新的 Promise 实例之后后面的 then() 就可以作为这个新 Promise 在成功后的回调函数。这种扁平化的写法更方便维护可读性更好并且可以更好的管理请求成功和失败的状态。
这段代码很经典你一定要多看几遍多默写几遍倒背如流也不过分。如果你平时的异步编程代码能写到这个水平说明你对 Promise 已经入门了因为绝大多数人都是用的这个写法。
其实还有更高级、更有水平的写法那就是用生成器、用 async ... await 来写Promise的链式调用也就是改进上面的十几行代码。你把它掌握了编程水平才能更上一层楼。我们稍后会讲。
Promise 链式调用举例封装 Node.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) {console.log(qianguyihao: data);});});});
上方代码多层嵌套存在回调地狱的问题。
Promise 写法 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) {console.log(qianguyihao: data);}).catch((err) {console.log(err);}); 用 async ... await 封装链式调用
前面讲的 Promise 链式调用是用 then().then().then() 这种写法。其实我们还可以用更高级的写法也就是用生成器、用 async ... await 改写那段代码。改进之后代码写起来非常简洁。
在学习这段内容之前你需要先去《JavaScript进阶/迭代器和生成器》那篇文章里去学习迭代器、生成器相关的知识。生成器是一种特殊的迭代器async ... await 是生成器的语法糖。
用生成器封装链式调用
代码举例 // 封装 Promise 链式请求function* getData(params_1) {// 【关键代码】const res1 yield requestData1(params_1);const res2 yield requestData2(res1);const res3 yield requestData3(res2);}// 调用 Promise 链式请求const generator getData(params_1);generator.next().value.then(res1 {generator.next(res1).value.then(res2 {generator.next(res2).value.then(res3 {generator.next(res3);})})})
生成器在执行时是分阶段执行的每次遇到 next()方法后就会执行一个阶段遇到 yield 就会结束当前阶段的执行并暂停。 上方代码中yield 后面的内容是当前阶段产生的 Promise 对象yield 前面的内容是要传递给下一个阶段的参数。
用 async ... await 封装链式调用重要
上面的生成器代码有些晦涩难懂实际开发中通常不会这么写。我们更喜欢用 async ... await 语法封装 Promise 的链式调用。async ... await 是属于生成器的语法糖写起来更简洁直观、更容易理解。
代码举例 // 封装用 async ... await 调用 Promise 链式请求async function getData() {const res1 await requestData1(params_1);const res2 await requestData2(res1);const res3 await requestData3(res2);}getData();
代码解释requestData1()、requestData2()、requestData3() 这三个函数都是一个Promise对象其内部封装的代码写法已经在前面「Promise 的嵌套写法」这一小段中讲过了。
上面的代码非常简洁。实际开发中也经常用到非常实用。暂时我们先记住用法下一章我们会学习 async ... await 的详细知识。 链式调用如何处理任务失败的情况
在链式调用多个异步任务的Promise时如果中间有一个任务失败或者异常要怎么处理呢是继续往下执行还是停止执行直接抛出异常这取决于你的业务逻辑是怎样的。
常见的处理方案有以下几种你可以根据具体情况按需选择。
统一处理失败的情况不继续往下走
针对 a、b、c 这三个请求不管哪个请求失败我都希望做统一处理。这种代码要怎么写呢?我们可以在最后面写一个 catch。
由于是统一处理多个请求的异常所以只要有一个请求失败了就会马上走到 catch剩下的请求就不会继续执行。比如说 a 请求失败然后会走到 catch不执行 b 和 c a 请求成功b 请求失败然后会走到 catch不执行 c。
代码举例如下 getPromise(a.json).then((res) {console.log(res);return getPromise(b.json); // 继续请求 b}).then((res) {// b 请求成功console.log(res);return getPromise(c.json); // 继续请求 c}).then((res) {// c 请求成功console.log(csuccess);}).catch((err) {// 统一处理请求失败console.log(err);});
中间的任务失败后如何继续往下走
在多个Promise的链式调用中如果中间的某个Promise 执行失败还想让剩下的其他 Promise 顺利执行的话那就请在中间那个失败的Promise里加一个失败的回调函数可以写到then函数的第二个参数里也可以写到catch函数里。捕获异常后便可继续往下执行其他的Promise。
代码举例
const promise1 new Promise((resolve, reject) {resolve(qianguyihao fulfilled 1);
});const promise2 new Promise((resolve, reject) {reject(qianguyihao rejected 2);
});const promise3 new Promise((resolve, reject) {resolve(qianguyihao fulfilled 3);
});promise1.then(res {console.log(res1:, res);// return 一个 失败的 Promisereturn promise2;}).then(res {console.log(res2:, res);return promise3;}, err {// 如果 promise2 为失败状态可以通过 then() 的第二个参数即失败的回调函数捕获异常然后就可以继续往下执行其他 Promiseconsole.log(err2:, err);// 关键代码即便 promise2 失败了也要继续执行 Promise3return promise3;}).then(res {console.log(res3, res);}, err {console.log(err3:, err);});
打印结果
res1: qianguyihao fulfilled 1
err2: qianguyihao rejected 2
res3 qianguyihao fulfilled 3
上方代码中我们单独处理了 promise2 失败的情况。不管promise2 成功还是失败我们都想让后续的 promise3 正常执行。 希望各位可以点个赞点个关注这对up真的很重要谢谢大家啦