原文: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
});
假设你有很多懒惰的朋友,因为你很着急想都给他们打电话。我们可以一个个的给他们打电话(链式操作)。
代码语言:javascript复制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
包含两个回调,第一个是成功的处理器,第二个是错误处理器。使用这两个处理器是完全没有问题的同时在多数情况下工作良好。它也有某些缺点:
- 1. 如果成功处理器中产生了错误,你将无法捕获或处理它;
- 2. 如果你像上面的链式例子一样使用链式调用,你需要在每个
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 秒钟所以她永远会赢得"比赛",然后打印出她的名字。