title: 06-Promise的链式调用
publish: true
Promise 的链式调用:处理多次 Ajax 请求【重要】
实际开发中,我们经常需要同时请求多个接口。比如说:在请求完接口1
的数据data1
之后,需要根据data1
的数据,继续请求接口 2,获取data2
;然后根据data2
的数据,继续请求接口 3。
换而言之,现在有三个网络请求,请求 2 必须依赖请求 1 的结果,请求 3 必须依赖请求 2 的结果,如果按照往常的写法,会有三层回调,会陷入“回调地狱”。
这种场景其实就是接口的多层嵌套调用。有了 Promise 之后,我们可以把多层嵌套调用按照线性的方式进行书写,非常优雅。也就是说:Promise 可以把原本的多层嵌套写法改进为链式写法。
ES5 中的传统写法
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 && success(xmlhttp.responseText); } else { fail && fail(new Error('接口请求失败')); } }; }
ajax( '/a.json', (res) => { console.log('qianguyihao 第一个接口请求成功:' + JSON.stringify(res)); ajax('b.json', (res) => { console.log('qianguyihao 第二个接口请求成功:' + JSON.stringify(res)); ajax('c.json', (res) => { console.log('qianguyihao 第三个接口请求成功:' + JSON.stringify(res)); }); }); }, (err) => { console.log('qianguyihao 请求失败:' + JSON.stringify(err)); } );
|
上面的代码层层嵌套,可读性很差,而且出现了我们常说的回调地狱问题。
Promise 链式调用(初步写法,方便理解)
如果我们不对 Promise 的链式调用进行封装,那么,它的简单写法是下面这样的:
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 && success(xmlhttp.responseText); } else { fail && 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 封装后的写法如下:
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 && success(xmlhttp.responseText); } else { fail && fail(new Error('接口请求失败')); } }; }
function getPromise(url) { return new Promise((resolve, reject) => { ajax(url, (res) => { if (res.retCode == 0) { resolve('request success' + res); } else { reject({ retCode: -1, msg: 'network error' }); } }); }); }
getPromise('a.json') .then((res) => { console.log(res); return getPromise('b.json'); }) .then((res) => { console.log(res); return getPromise('c.json'); }) .then((res) => { console.log(res); }) .catch((e) => { console.log(e); });
|
怎么样?上面代码是不是非常简洁?而且可读性很强。
代码写到这里,我们还可以再继续优化一下。细心的你可以发现,我们在做三次嵌套请求的时候,针对 resolve 和 reject 的处理时机是一样的。如果你的业务是针对同一个接口连续做了三次调用,只是请求传参不同,那么,按上面这样写是没有问题的。
但是,真正在实战中,我们往往需要嵌套请求多个不同的接口,要处理的 resolve 和 reject 的时机和逻辑往往是不同的,所以需要分开封装不同的 Promise 实例,这在实战开发中更为常见。代码应该是像下面这样写。
Promise 链式调用(封装多个接口)
针对多个不同接口的嵌套调用,采用 promise 封装后的写法如下:
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 && success(xmlhttp.responseText); } else { fail && fail(new Error('接口请求失败')); } }; }
function request1() { return new Promise((resolve, reject) => { ajax('https://www.baidu.com', (res) => { if (res.retCode == 201) { resolve('request1 success' + res); } else { reject('接口1请求失败'); } }); }); }
function request2() { return new Promise((resolve, reject) => { ajax('https://www.jd.com', (res) => { if (res.retCode == 202) { resolve('request2 success' + res); } else { reject('接口2请求失败'); } }); }); }
function request3() { return new Promise((resolve, reject) => { ajax('https://www.taobao.com', (res) => { if (res.retCode == 203) { resolve('request3 success' + res); } else { reject('接口3请求失败'); } }); }); }
request1() .then((res1) => { console.log(res1); return request2(); }) .then((res2) => { console.log(res2); return request3(); }) .then((res3) => { console.log(res3); }) .catch((err) => { console.log(err); });
|
这段代码很经典,你一定要多看几遍,多默写几遍。倒背如流也不过分。
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) { fs.readFile(D, '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) => { return read(D); }) .then((data) => { console.log('qianguyihao:' + data); }) .catch((err) => { console.log(err); });
|
这一段代码可以看出,Promise 很好的处理了回调地狱的问题。下一篇文章,我们会更详细的介绍 Promise 的链式调用。
链式调用,如何处理 reject 失败状态
例 1:不处理 reject
getPromise('a.json') .then( (res) => { console.log(res); return getPromise('b.json'); }, (err) => { console.log('a: err'); } ) .then((res) => { console.log(res); return getPromise('c.json'); }) .then((res) => { console.log('c:success'); });
|
上面的代码中,假设 a 请求失败,那么,后面的代码会怎么走呢?
打印结果:
我们可以看到,虽然 a 请求失败,但后续的请求依然会继续执行。
为何打印结果的第二行是 undefined?这是因为,当 a 请求走到 reject 之后,我们并没有做任何处理。这就导致,代码走到第二个 then
的时候,其实是在执行一个空的 promise。
例 2:单独处理 reject
getPromise('a.json') .then( (res) => { console.log(res); return getPromise('b.json'); }, (err) => { console.log('a: err'); return getPromise('b.json'); } ) .then((res) => { console.log(res); return getPromise('c.json'); }) .then((res) => { console.log('c:success'); });
|
跟例 1 相比,例 2 在 reject 中增加了一行return getPromise('b.json')
,意味着,即使 a 请求失败,也要继续执行 b。
这段代码,我们是单独处理了 a 请求失败的情况。
统一处理 reject
针对 a、b、c 这三个请求,不管哪个请求失败,我都希望做统一处理。这种代码要怎么写呢?我们可以在最后面写一个 catch。
代码举例如下:
getPromise('a.json') .then((res) => { console.log(res); return getPromise('b.json'); }) .then((res) => { console.log(res); return getPromise('c.json'); }) .then((res) => { console.log('c:success'); }) .catch((err) => { console.log(err); });
|
上面的代码中,由于是统一处理多个请求的异常,所以只要有一个请求失败了,就会马上走到 catch,剩下的请求就不会继续执行了。比如说:
return 的返回值
return 后面的返回值,有两种情况:
我们针对上面这两种情况,详细解释一下。
情况 1:返回 Promise 实例对象
举例如下:
getPromise('a.json') .then((res) => { console.log(res); return new Promise((resolve, reject) => { resolve('qianguyihao'); }); }) .then((res) => { console.log(res); });
|
情况 2:返回 普通值
getPromise('a.json') .then((res) => { console.log(res); return 'qianguyihao'; })
.then((res2) => { console.log(res2); });
|
我的公众号
想学习更多技能?不妨关注我的微信公众号:千古壹号。
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外: