原文:https://dev.to/bhagatparwinder/promises-chaining-error-handling-operators-3ccb
上篇文章详细的介绍了什么是 promise 以及如何创建、 resolve 和 reject。
这一次,我们将讨论 promise 中的链式操作以及错误处理和可用的运算符。
链式
回调函数最显著的缺点之一是当我们连接它们时形成的嵌套结构,在 then 的帮助下,我们可以创建一个更易阅读、理解和调试的扁平结构。
假设我们有一个 waitForMe
的函数返回 promise,这个函数等待 2 秒后会返回你朋友的名字。
const waitForMe = function(name) { return new Promise((resolve, reject) => { setTimeout(() => { return resolve(name); }, 2000); });}waitForMe("Parwinder") .then((data) => { console.log(data); // Outputs/yells "Parwinder" after 2 second });
假设你有很多懒惰的朋友,因为你很着急想都给他们打电话。我们可以一个个的给他们打电话(链式操作)。
waitForMe("Parwinder") .then((data) => { console.log(data); // waits 2 seconds and outputs "Parwinder" return waitForMe("Lauren"); }) .then((data) => { console.log(data); // waits another 2 seconds and outputs "Lauren" return waitForMe("Robert"); }) .then((data) => { console.log(data); // waits another 2 seconds and outputs "Robert" return waitForMe("Eliu"); }) .then((data) => { console.log(data); // waits another 2 seconds and outputs "Eliu" })
你会看到我们是如何用链式调用名字的以及控制台间隔 2 秒打印出它们,每一个 then
操作符会返回一个 promise 然后和其他的 then
链起来,同时保持代码结构的扁平。
错误处理
在 promise 的链式中有两种方法可以处理错误,要么在 then
块中传入错误处理器或者使用 catch
操作符。我们已经在前一篇文章中讨论了第一种方法。
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { reject("an error has occurred"); }, 2000)});myPromise.then((response) => { console.log(response);}, (error) => { console.log(error); // an error has occurred});
在上面的例子中,then
包含两个回调,第一个是成功的处理器,第二个是错误处理器。使用这两个处理器是完全没有问题的同时在多数情况下工作良好。它也有某些缺点:
then
块中添加错误处理器。为了解决这些缺点,我们使用 catch
操作符。
const myPromise = new Promise((resolve, reject) => { setTimeout(() => { reject("an error has occurred"); }, 2000)});myPromise.then((response) => { console.log(response);}).catch((error) => { console.log(error); // an error has occured});
在 promise 的链式调用中,我们可以这样使用 catch
操作符:
const waitForMe = function (name) { return new Promise((resolve, reject) => { if (name === "Robert") { return reject("Robert is always on time"); } else { setTimeout(() => { return resolve(name); }, 2000); } });}waitForMe("Parwinder") .then((data) => { console.log(data); // wait 2 second and log "Parwinder" return waitForMe("Lauren"); }) .then((data) => { console.log(data); // wait 2 more seconds and log "Lauren" return waitForMe("Robert"); // this will result in promise rejection }) .then((data) => { console.log(data); // this never gets executed return waitForMe("Eliu"); }) .then((data) => { console.log(data); // this never gets executed }) .catch((error) => { console.log(error); // Robert is always on time })
记住在 promise 的链式调用中一旦有一个产生错误后续的链将会被终止。这也是为什么最后两个打印没有执行。
catch
操作符并不总是必须添加到最后,它可以添加到链式的中间然后可以捕获到它那个位置之前的错误。
const waitForMe = function (name) { return new Promise((resolve, reject) => { if (name === "Robert") { return reject("Robert is always on time"); } else { setTimeout(() => { return resolve(name); }, 2000); } });}waitForMe("Parwinder") .then((data) => { console.log(data); // wait 2 second and log "Parwinder" return waitForMe("Lauren"); }) .then((data) => { console.log(data); // wait 2 more seconds and log "Lauren" return waitForMe("Robert"); // this will result in promise rejection }) .catch((error) => { // catches the promise rejection console.log(error); // Robert is always on time return waitForMe("Eliu"); // continues the chain }) .then((data) => { console.log(data); // Eliu })
注意: 为什么不一直使用 catch
而忽略 then
中的错误处理器呢?
我上面提到过 then 的劣势:
需要为每一个 then 添加一个错误处理器。
有时候你可能需要在链式 then
的错误处理器中有不同的错误处理方式,基于这一点,then 中独立的错误处理器可能会更有优势。
操作符
promise 上有两个重要的操作符,它们分别适应特定的场景:Promise.all
和 Promise.race
。
Promise.all
当你在一个异步操作后执行另一个(串行),promise 的链式调用很顺手。经常,你需要多个异步操作并行执行而不是等一个执行完成后再执行。另外,你的操作依赖所有的异步操作的完成情况。
Promise.all
使我们可以同时执行多个异步操作,但依旧需要等到它们都完成 了才执行回调。
const waitForMe = function (name) { return new Promise((resolve, reject) => { setTimeout(() => { return resolve(name); }, 2000); });}const firstPromise = waitForMe("Parwinder");const secondPromise = waitForMe("Lauren");const thirdPromise = waitForMe("Robert");const fourthPromise = waitForMe("Eliu");Promise.all([firstPromise, secondPromise, thirdPromise, fourthPromise]) .then((data) => { console.log(data); // [ 'Parwinder', 'Lauren', 'Robert', 'Eliu' ] });
上面的例子同时执行了 promise,等到它们都返回 name 就会输出一个结果的数组。这种方式执行耗费 2 秒,链式的形式则耗费 8 秒来输出四个名字。
数组中输出顺序是严格与输入 Promise.all
中的顺序是一致的。
注意: Promise.all
中即使有一个错误产生,整个结果都会失败。
const waitForMe = function (name) { return new Promise((resolve, reject) => { if (name === "Robert") { return reject("Robert is always on time"); } else { setTimeout(() => { return resolve(name); }, 2000); } });}const firstPromise = waitForMe("Parwinder");const secondPromise = waitForMe("Lauren");const thirdPromise = waitForMe("Robert");const fourthPromise = waitForMe("Eliu");Promise.all([firstPromise, secondPromise, thirdPromise, fourthPromise]) .then((data) => { console.log(data); }) .catch((error) => { console.log(error); // Robert is always on time })
它会忽略其他成功的 promise,若有多个错误它会返回输入 Promise.all
中数组的第一个发生错误的 promise。
const waitForMe = function (name) { return new Promise((resolve, reject) => { if (name === "Robert") { return reject("Robert is always on time"); } else if (name === "Lauren") { return reject("Lauren is always on time"); } else { setTimeout(() => { return resolve(name); }, 2000); } });}const firstPromise = waitForMe("Parwinder");const secondPromise = waitForMe("Lauren");const thirdPromise = waitForMe("Robert");const fourthPromise = waitForMe("Eliu");Promise.all([firstPromise, secondPromise, thirdPromise, fourthPromise]) .then((data) => { console.log(data); }) .catch((error) => { console.log(error); // Lauren is always on time })
Promise.race
Promise.race
处理一个特殊的情形,当你需要同时执行多个异步操作,但不需要等到它们全部完成。相反,你想当第一个 promise 完成后尽快执行回调。
const waitForMe = function (name, time) { return new Promise((resolve, reject) => { setTimeout(() => { return resolve(name); }, time); });}const firstPromise = waitForMe("Parwinder", 4000);const secondPromise = waitForMe("Lauren", 3000);const thirdPromise = waitForMe("Robert", 7000);const fourthPromise = waitForMe("Eliu", 5000);Promise.race([firstPromise, secondPromise, thirdPromise, fourthPromise]) .then((data) => { console.log(data); // Lauren }) .catch((error) => { console.log(error); })
我为 setTimeout
添加了一个参数,跟着每一个名字我传入了不同的时间,"Lauren" 只有 3 秒钟所以她永远会赢得"比赛",然后打印出她的名字。