我如何使用 JavaScript Promes
原文发表于Shubho.Dev
JavaScript 的异步编程对我来说曾经很可怕。我唯一熟悉的异步范式是 jQuery 的$.ajax。然而,过去八年我一直使用原生 JavaScript,当我开始接触 NodeJS 时,我不得不学习 Promise。我很少接触像 Bluebird 这样的第三方库。我最熟悉的是原生 Promise。
我以前对异步编程范式(或者说整个异步编程范式)的主要不满Promise在于,当我想在 Promise 语句执行完毕后再执行其他语句时。我花了好长时间才意识到,一旦 Promise 语句执行完毕,就无法取消它。另一个问题是 Promise 链式调用。这真是个棘手的问题。我早期使用 Promise 编写的函数总是看起来像是陷入了回调地狱。经过这么多年,也参与过几个大型项目之后,我可以很肯定地说,我爱上了 Promise。即使 async/await 现在是主流,我依然钟情于 Promise。
以下是我如何使用 Promise 来简化我的编码生活。
创建 Promise 框架
每当我创建一个返回 Promise 的新函数时,我都会先创建函数框架。由于该函数只能返回 Promise 类型的值,所以我总是将函数中的所有语句都包裹在 Promise 对象内。
function sample() {
return new Promise(function(resolve, reject) {
// The function body
});
}
上面的示例函数将其所有语句都封装在 Promise 中,并立即返回。你可以从函数体中resolve()获取reject()所需的输出。这样,我就不会犯 Promise 没有返回的错误。这也有助于我创建 Promise 链。每当我在链中发现需要一个新的函数时,我都会创建一个框架函数,并为其命名,然后完成主链。之后,我会逐个返回并完成各个函数。
承诺链——需要记住的要点
Promise 链式调用很棘手。如果我们不小心,可能会陷入一种新型的回调地狱。例如:
function promiseCallback() {
return new Promise((resolve, reject) => {
aNewFunction()
.then((values) => {
someOtherFunction(values)
.then((someOtherValue) => {
// Do something
resolve(someOtherValue);
})
.catch((err1) => {
// Error in inner function
reject(err1);
});
})
.catch((err) => {
// Error in outer function
reject(err);
});
});
}
在上面的示例中,`then`aFunction()和 ` someOtherFunction()catch` 是两个返回 Promise 的函数。仔细观察,你会发现它们的执行顺序看起来像是一个回调地狱。内部的 `then` 和 `catch` 会捕获回调链,而外部的回调函数则是独立的。我们不能在同一个 `catch` 块中处理错误,而且必须确保内部函数始终位于其外部回调的最后一行,then()否则我们将无法控制执行流程。
链条的更好用法:
function promiseCallback() {
return new Promise((resolve, reject) => {
aNewFunction()
.then((values) => {
return someOtherFunction(values);
})
.then((someOtherValue) => {
// Do something
resolve(someOtherValue);
})
.catch((err) => {
// Error in outer function
reject(err);
});
});
}
链式返回then只能有三种类型:
- Promise——
then链式调用中的一个函数可以返回一个 Promise。它的结果会传递给下一个函数then。 - 标量值-
then链式函数可以返回一个值,例如字符串或数字。该值将then原样传递给下一个函数,链式函数可以继续执行。 - 抛出-
then函数可以throw抛出错误,这将使执行移至 catch 代码块。
只要你所有的返回值都符合then上述三种类型,那么你的 Promise 链就不会出现问题。
注意:
请记住始终在链条的最后resolve()或最末端。reject()thencatch
何时创建新的 Promise 函数
在 Promise 链中,如果存在多个 if-else 条件,且每个条件都可能导致不同的 Promise 结果,那么此时创建一个返回 Promise 的新函数是绝佳的选择。这样,Promise 链就只返回调用该新函数的一条语句。
一步处理标量值或 Promise 函数
假设我们有一个函数,可以根据学生的学号获取其考试成绩。但是,该函数只能接受学号或学生姓名作为输入。实际上,只有通过学号才能从数据库中获取成绩。以下是伪代码。
function getMarks(obj) {
let rollNumberPromise = null;
if ('rollNumber' in obj) {
rollNumberPromise = Promise.resolve(obj.rollNumber);
} else if ('studentName' in obj) {
rollNumberPromise = getRollNumberFromName(obj.studentName);
}
if (!rollNumberPromise) {
reject('Nothing worked');
}
rollNumberPromise
.then((rollNumber) => {
return get_marks_from_db(rollNumber);
})
.then((marks) => {
resolve(marks);
})
.catch((err) => {
reject(err);
});
}
function getRollNumberFromName(studentName) {
return new Promise(function(resolve, reject) {
fn_to_get_roll_number_from_db(studentName)
.then((rollNumber) => {
resolve(rollNumber);
})
.catch((err) => {
reject(err);
});
});
}
function fn_to_get_roll_number_from_db(studentName) {
return new Promise(function(resolve, reject) {
// some code
});
}
function get_marks_from_db(rollNumber) {
return new Promise(function(resolve, reject) {
// some code
});
}
getMarks(obj)该函数接受一个对象作为输入。我们创建一个局部变量rollNumberPromise。如果 rollNumber 已存在,则使用 `.` 将其值保存到该变量中Promise.resolve()。这将创建一个 Promise,当传入该值时,该 Promise 将被解析。如果传入的是学生姓名,则我们将函数调用保存到getRollNumberFromName(studentName)该局部变量中。rollNumberPromise.then()无论 rollNumber 是从数据库接收的还是直接作为输入传递给函数的,调用该函数都会返回一个 rollNumber。这种方式确保函数getMarks()只有一个 Promise 链,而不是基于传入的输入是数字还是姓名的 if-else 条件语句。
在结尾处做出承诺
如前所述,Promise 一旦被调用,就无法取消。任何不依赖于 Promise 输出且无需异步调用即可独立执行的语句,都应该在函数中启动 Promise 链之前完成。一旦 Promise 链开始,任何后续步骤都必须在该then链内执行。唯一的例外情况是,当你不关心 Promise 的值,并且希望 Promise 在后台执行,而你的主函数继续运行时。
结论
承诺很难用。但是,只要勤加练习并遵循一些规则,使用承诺就会变得轻松愉快。我严格遵守上述规则,现在使用承诺从未出错。找到适合自己的方法,并制定自己的规则。
文章来源:https://dev.to/shubho/how-i-use-javascript-promises-19c6