JavaScript Promises 和异步编程简介
承诺
方法
Async 和 Await
结论
立即在http://jauyeung.net/subscribe/订阅我的电子邮件列表
在 Twitter 上关注我:https://twitter.com/AuMayeung
更多文章请访问https://medium.com/@hohanga
由于 JavaScript 是单线程语言,同步代码一次只能执行一行。这意味着,如果同步代码的运行时间超过瞬时,它将停止其余代码的运行,直到正在运行的代码完成为止。为了防止运行时间不确定的代码阻碍其他代码的运行,我们需要使用异步代码。
承诺
要在 JavaScript 中实现这一点,我们可以在代码中使用 Promise。Promise 是表示运行时间不确定的进程的对象,它可能导致成功或失败。要在 JavaScript 中创建 Promise,我们使用一个Promise用于创建 Promise 的构造函数对象。Promise构造函数接受一个名为 executor 的函数,该函数具有resolve和reject参数。这两个参数都是函数,我们调用它们来履行 Promise(这意味着以某个值成功结束它)或拒绝 Promise(这意味着返回某个错误值并将 Promise 标记为失败)。函数的返回值将被忽略。因此,Promise 不能返回除 Promise 之外的任何值。
例如,我们可以在 JavaScript 中定义一个承诺,如以下代码所示:
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('abc'), 1000);
});
上面的代码创建了一个 Promise,它会abc在一秒后使用值来实现该 Promise。因为我们setTimeout在执行器函数内部运行,以便abc在一秒内使用值来解决该 Promise,所以这是异步代码。我们无法abc在的回调函数中返回值setTimeout,所以我们必须调用resolve('abc')来获取已解析的值。我们可以使用then函数访问已实现 Promise 的解析值。该then函数接受一个回调函数,该回调函数将已实现 Promise 的解析值作为参数。您可以在那里获取该值并对其执行所需的操作。例如,我们可以执行以下操作:
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('abc'), 1000);
});
promise.then((val) => {
console.log(val);
})
当我们运行上面的代码时,我们应该会看到abc日志。正如我们所见,当 Promise 通过调用函数实现时,Promise 将会提供相应的值resolve。
一个 Promise 有三种状态。它可以是 pending,表示该 Promise 已被 fulfilled 或 rejected。它可以是 fulfilled,表示操作已成功完成。也可以是 rejection,表示该 Promise 操作失败。
待处理的 Promise 可以以某个值完成,也可以以某个错误被拒绝。当 Promise 完成时,函数会获取相应的解析值then,并调用传入函数的回调函数then。如果 Promise 被拒绝,我们可以选择使用函数捕获错误catch,该函数也接受一个带有错误信息的回调函数。then和catch函数都返回 Promise,因此它们可以链接在一起。
例如,我们可以写如下内容:
const promise = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (num === 1) {
resolve('resolved')
} else {
reject('rejected')
}
}, 1000);
});
}
promise(1)
.then((val) => {
console.log(val);
})
.catch((error) => {
console.log(error);
})promise(2)
.then((val) => {
console.log(val);
})
.catch((error) => {
console.log(error);
})
在上面的代码中,我们有一个函数,它返回一个 JavaScript Promise,当值为 1 时,promise它会执行 Promise 的值,否则会抛出错误并拒绝 Promise 。因此我们运行:resolvednumrejected
promise(1)
.then((val) => {
console.log(val);
})
.catch((error) => {
console.log(error);
})
然后then函数运行,由于值为 1,函数调用返回的 Promisepromise(1)被满足,num并且解析后的值可以在 中设置val。因此,运行时console.log(val)会resolved记录日志。运行以下代码:
promise(2)
.then((val) => {
console.log(val);
})
.catch((error) => {
console.log(error);
})
然后catch函数运行,因为函数调用返回的 Promisepromise(2)被拒绝了,而这个被拒绝的错误值可以在 error 中设置。所以运行时console.log(error)会rejected打印日志。
JavaScript 的 Promise 对象具有以下属性:length和prototype。length的值为 1,表示构造函数参数的数量,即始终为 1。prototype属性表示 Promise 对象的原型。
Promise 还具有一个finally方法,用于运行无论 Promise 是 fulfilled 还是 rejected 都会运行的代码。该finally方法接受一个回调函数作为参数,无论 Promise 的结果如何,任何你想要运行的代码都会在这个回调函数中运行。例如,如果我们运行:
Promise.reject('error')
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
console.log('finally runs');
})
然后我们获取error并finally runs记录,因为原始承诺因原因而被拒绝error。然后该方法回调中的任何代码finally都会运行。
使用 Promise 编写异步代码的主要好处是我们可以使用它们按顺序运行异步代码。为此,我们可以将 Promise 与then函数链接起来。该then函数接受一个在 Promise 实现时运行的回调函数。当 Promise 被拒绝时,它还接受第二个参数。要链接 Promise,我们必须返回另一个 Promise 作为函数第一个回调函数的返回值then。如果我们不想将另一个 Promise 链接到现有的 Promise,它可以返回其他值,比如什么也不返回。我们可以返回一个值,该值将被解析并且可以在下一个then函数中检索到。它也可以抛出一个错误。然后,返回的 Promisethen将被拒绝,并将抛出的错误作为值。它还可以返回一个已经实现或被拒绝的 Promise,当一个函数链接在它之后时,它将获得实现的值then,或者在函数的回调中获取错误原因catch。
例如,我们可以写以下内容:
Promise.resolve(1)
.then(val => {
console.log(val);
return Promise.resolve(2)
})
.then(val => {
console.log(val);
})Promise.resolve(1)
.then(val => {
console.log(val);
return Promise.reject('error')
})
.then(val => {
console.log(val);
})
.catch(error => console.log(error));Promise.resolve(1)
.then(val => {
console.log(val);
throw new Error('error');
})
.then(val => {
console.log(val);
})
.catch(error => console.log(error));
在第一个例子中,我们将 Promise 串联起来,它们都解析为一个值。所有 Promise 都已解析为值。在第二个和最后一个例子中,我们拒绝第二个 Promise 或抛出一个错误。它们的作用相同。第二个 Promise 被拒绝,错误原因将记录在catch函数的回调中。我们也可以串联待处理的 Promise,如下例所示:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 1000);
});
promise1
.then(val => {
console.log(val);
return promise2;
})
.then(val => {
console.log(val);
})
.catch(error => console.log(error));
第一个函数的回调then返回promise2,这是一个待处理的承诺。
方法
JavaScript 承诺具有以下方法。
Promise.all(可迭代)
Promise.all接受一个可迭代对象,该对象允许我们在某些计算机上并行运行多个 Promise,而在其他计算机上串行运行。这对于运行多个互不依赖的 Promise 非常方便。它接受一个包含 Promise 列表(通常为数组)的可迭代对象,然后返回一个 Promise,Promise当迭代对象中的所有 Promise 都解析后,该 Promise 也解析成功。
例如,我们可以编写如下代码来运行多个承诺Promise.all:
const promise1 = Promise.resolve(1);
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve(3), 1000);
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values);
});
如果我们运行上面的代码,应该console.log会打印日志[1,2,3]。如我们所见,它仅在所有 Promise 都已完成后才返回已解决的值。如果其中一些 Promise 被拒绝,那么我们将不会获得任何已解决的值。相反,我们将获得被拒绝的 Promise 返回的任何错误值。它会在第一个被拒绝的 Promise 处停止,并将该值发送给catch函数的回调函数。例如,如果我们有:
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => reject(3), 1000);
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
.catch(error => {
console.log(error);
});
console.log然后我们从函数的回调中得到记录的 2 catch。
Promise.allSettled
Promise.allSettled返回一个 Promise,该 Promise 在所有给定的 Promise 都解析或拒绝后解析。它接受一个可迭代对象,其中包含一组 Promise,例如一个 Promise 数组。返回的 Promise 的解析值是一个包含每个 Promise 最终状态的数组。例如,假设我们有:
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => reject(3), 1000);
});
Promise.allSettled([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
如果我们运行上面的代码,我们会得到一个包含三个条目的数组,每个条目都是一个对象,它包含已完成的 Promise 的status和属性,以及一个包含已拒绝的 Promise 的和属性。例如,上面的代码将记录、、。成功 Promise 的状态会被记录,拒绝 Promise 的状态也会被记录。valuestatusreason{status: “fulfilled”, value: 1}{status: “rejected”, reason: 2}{status: “rejected”, reason: 3}fulfilledrejected
Promise.race
该Promise.race方法返回一个 Promise,该 Promise 解析为第一个已完成的 Promise 的已解析值。它接受一个可迭代对象,其中包含一组 Promise,例如一个 Promise 数组。如果传入的可迭代对象为空,则返回的 Promise 将永远处于待处理状态。如果可迭代对象包含一个或多个非 Promise 值,或者包含已完成的 Promise,则将Promise.race返回其中第一个。例如,如果我们有:
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve(3), 1000);
});
Promise.race([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
然后我们看到它1被打印出来了。这是因为promise1它是第一个被解析的,因为它在下一行运行之前就被立即解析了。同样,如果我们在数组中有一个非 Promise 值作为参数传入,就像下面的代码一样:
const promise1 = 1;
const promise2 = Promise.resolve(2);
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve(3), 1000);
});
Promise.race([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
然后,我们得到相同的输出,因为它是我们传递给方法的数组中的非 Promise 值Promise.race。同步代码始终在异步代码之前运行,因此同步代码的位置无关紧要。如果我们有:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 1000);
});
const promise3 = 3;
Promise.race([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
然后我们3进行记录,因为setTimeout将回调函数放入队列中以便稍后运行,所以它将比同步代码更晚运行。
最后,如果我们有:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 1000);
});
Promise.race([promise1, promise2])
.then((values) => {
console.log(values);
})
然后我们2进入控制台日志,因为一秒钟内解决的承诺将比两秒钟内解决的承诺更早得到解决。
Promise.reject
Promise.reject返回一个因原因而被拒绝的 Promise。使用 的实例对象来拒绝 Promise 很有用Error。例如,如果我们有以下代码:
Promise.reject(new Error('rejected'))
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
})
然后我们就rejected登录了。
Promise.resolve
Promise.resolve返回一个 Promise,该 Promise 解析为传递给函数参数的值resolve。我们还可以传入一个具有then属性的对象,其值是 Promise 的回调函数。如果该值包含then方法,则该 Promise 将使用该函数实现的值来实现then。也就是说,函数的第一个参数与函数的值then相同resolve,第二个参数与函数的值相同。reject.例如,我们可以编写以下内容:
Promise.resolve(1)
.then((value) => {
console.log(value);
})
然后我们得到1记录,因为1这是我们传递给函数的值resolve,以返回具有已解析值的承诺1。
如果我们传入一个带有then方法的对象,如下面的代码所示:
Promise.resolve({
then(resolve, reject) {
resolve(1);
}
})
.then((value) => {
console.log(value);
})
然后我们得到1记录的值。这是因为该Promise.resolve函数将运行该then函数,并且resolve设置为then属性的函数参数将被假定为resolve在 Promise 中调用该函数的函数。如果我们在传入的对象中调用reject该函数内部的函数then,那么我们会得到一个被拒绝的 Promise,如下例所示:
Promise.resolve({
then(resolve, reject) {
reject('error');
}
})
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
})
在上面的代码中,error由于承诺被拒绝,我们进行了记录。
Async 和 Await
使用async和await,我们可以缩短 Promise 代码。在使用async和之前await,我们必须使用then函数,并且必须将回调函数作为所有then函数的参数。由于 Promise 很多,这会使代码变得很长。相反,我们可以使用async和await语法来替换then函数及其关联的回调。例如,我们可以将以下代码从:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 1000);
});
promise1
.then((val1) => {
console.log(val1);
return promise2;
})
.then((val2) => {
console.log(val2);
})
到:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 1000);
});
(async () => {
const val1 = await promise1;
console.log(val1)
const val2 = await promise2;
console.log(val2)
})()
我们将then和 回调替换为await。然后,我们可以将每个 Promise 的解析值赋值为变量。请注意,如果我们使用 来await作为 Promise 代码,则必须async像上例一样添加函数签名。为了捕获错误,catch我们使用 子句,而不是将函数链接在末尾catch。此外,我们不是finally在底部链接函数以在 Promise 结束时运行代码,而是在 子句finally后使用 子句catch。
例如,我们可以写:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject('error'), 1000);
});
(async () => {
try {
const val1 = await promise1;
console.log(val1)
const val2 = await promise2;
console.log(val2)
} catch (error) {
console.log(error)
} finally {
console.log('finally runs');
}})()
在上面的代码中,我们将 Promise 的解析值赋给了一个变量,而不是像上一行then那样在函数的回调中获取值const response = await promise1。此外,我们使用了try...catch...finally代码块来捕获被拒绝的 Promise 的错误,并使用了finally子句而不是finally传入回调的函数来创建无论 Promise 发生什么都会运行的代码。
与其他使用 Promise 的函数一样,async函数始终返回 Promise,不能返回任何其他内容。在上面的示例中,我们展示了如何以比将then回调作为参数传入的函数更简洁的方式链接 Promise。
结论
有了 Promise,我们可以轻松编写异步代码。Promise 是表示一个运行时间不确定的进程的对象,该进程可能成功也可能失败。要在 JavaScript 中创建 Promise,我们使用一个Promise构造函数对象。
构造Promise函数接受一个名为 executor 的函数,该函数包含resolve和reject参数。这两个参数都是函数,我们调用它们来要么实现 Promise(这意味着成功结束并返回某个值),要么拒绝 Promise(这意味着返回某个错误值并将 Promise 标记为失败)。该函数的返回值会被忽略。因此,Promise 不能返回除 Promise 之外的任何值。
Promise 是可链式执行的,因为 Promise 会返回 Promise。Promisethen的函数可以抛出错误、返回已解决的值,或者返回其他处于待处理、已完成或已拒绝状态的 Promise。
后端开发教程 - Java、Spring Boot 实战 - msg200.com