关于 Promises、Thenables 和惰性求值你需要知道的一切
本文译自:Everything You Need to Know About Promises, Thenables, & Lazy-Evaluation
译者按:js Promises sleep 失效、未按照预期时间执行,解决方法及原因。
新的一年开始了,虽然很多人都承诺要更加活跃,但我将向您展示如何让 Promise
我们变得更懒惰…… Promise
s。
稍后会更有意义。
首先,让我们看一个基本的 Promise
例子。在这里,我有一个名为 sleep 的函数,它需要以毫秒为单位的时间和一个值。它返回一个承诺,该承诺将执行 setTimeout
我们应该等待的毫秒数;然后 Promise 使用值解析。
/** * @template ValueType * @param {number} ms * @param {ValueType} value * @returns {Promise<ValueType>} */
function sleep(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
它是这样工作的:
带有代码“await sleep(1000, ‘Yawn & stretch’)”的 JavaScript 控制台。然后一秒钟后,“‘打哈欠和伸展’”
我们可以 使用参数 和 等待sleep
函数, 一秒钟后, 将记录字符串“Yawn & stretch”。1000``'Yawn & stretch'``console
这没什么特别的。它的行为可能与您预期的一样,但是如果我们将它存储为变量以供 await
稍后使用,而不是 立即await
返回 ,它会变得有点奇怪Promise
。
const nap = sleep(1000, 'Yawn & stretch')
现在,假设我们做一些其他需要时间的工作(比如输入下一个示例),然后 await
是 nap
变量。
在 JavaScript 控制台中输入“await nap”并立即看到响应“‘Yawn & stretch’”
您可能希望在解析之前延迟一秒钟,但实际上它会立即解析。每当您创建一个 Promise
时,您都会实例化它所负责的任何异步功能。
在我们的示例中,当我们定义 nap
变量时, Promise
将创建执行 setTimeout
. 因为我是一个慢打字者,我们 Promise
会解决的 await
。
换句话说, Promise
s 是渴望的。他们不等你给 await
他们。
在某些情况下,这是一件好事。在其他情况下,它可能会导致不必要的资源使用。对于这些场景,您可能想要一些看起来像 Promise
,但使用 惰性评价 仅在需要时实例化。
在我们继续之前,我想向您展示一些有趣的东西。
Promise
s 不是唯一可以 await
在 JavaScript 中编辑的东西。如果我们 Object
用一个 .then()
方法创建一个 plain,我们实际上可以 await
像任何 Promise
.
JavaScript 控制台显示文本“await { then: () => console.log(‘🙃’) }”,后跟“🙃”。
这有点奇怪,但它也允许我们创建 看起来 像 Promise
s 但不是的不同对象。这些对象有时被称为“然后能“.
考虑到这一点,让我们创建一个新的 班级 称为 LazyPromise
扩展内置 Promise
构造函数。扩展 Promise 并不是绝对必要的,但它使它看起来更类似于 Promise
使用 instanceof
.
class LazyPromise extends Promise {
/** @param {ConstructorParameters<PromiseConstructor>[0]} executor */
constructor(executor) {
super(executor);
if (typeof executor !== 'function') {
throw new TypeError(`LazyPromise executor is not a function`);
}
this._executor = executor;
}
then() {
this.promise = this.promise || new Promise(this._executor);
return this.promise.then.apply(this.promise, arguments);
}
}
要关注的部分是 then()
方法。它劫持了标准的默认行为, Promise
等待 .then()
方法执行后再创建真正的 Promise
.
这避免了在您实际调用它之前实例化异步功能。无论您显式调用 .then()
还是使用 它,它都有效await
。
现在,让我们看看如果我们将 Promise
原始 sleep
函数中的 替换为 a 会发生什么LazyPromise
。再一次,我们将结果分配给一个 nap
变量。
function sleep(ms, value) {
return new LazyPromise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
const nap = sleep(1000, 'Yawn & stretch')
然后我们花时间输入该 await nap
行并执行它。
在 JavaScript 控制台中输入“await nap”并在一秒钟的延迟后看到响应“‘Yawn & stretch’”
Promise
这一次,无论变量创建后经过了多少时间,我们都会在解析前看到一秒的延迟 。
(请注意,此实现仅创建 Promise
一次新的并在后续调用中引用它。因此,如果我们 await
再次使用它,它将像任何正常情况一样立即解析 Promise
)
当然,这是一个您可能不会在生产代码中找到的微不足道的示例,但是有许多项目使用类似惰性求值 Promise
的对象。可能最常见的示例是数据库 ORM和查询构建器,例如 Knex.js 或者 棱镜.
考虑下面的伪代码。它的灵感来自其中一些查询构建器:
const query = db('user')
.select('name')
.limit(10)
const users = await query
我们创建一个数据库查询,该查询转到该 "user"
表并选择前十个条目并返回它们的名称。从理论上讲,这对于常规 Promise
.
但是如果我们想根据查询字符串参数等特定条件修改查询怎么办?能够在最终等待 Promise
.
const query = db('user')
.select('name')
.limit(10)
if (orderBy) {
query.orderBy(orderBy)
}
if (limit) {
query.limit(limit)
}
if (id) {
query.where({ id: id })
}
const users = await query
如果原始数据库查询是标准的 Promise
,它会在我们分配变量后立即实例化查询,我们以后将无法修改它。
通过惰性求值,我们可以编写这样更容易理解的代码,改善开发人员体验,并且只在需要时执行一次查询。
这是惰性评估很棒的一个例子。它也可能对构建、修改和编排 HTTP 请求等有用。
Lazy Promise
对于正确的用例来说非常酷,但这并不是说它们应该替换每个 Promise
. 在某些情况下,尽快实例化并尽快准备好响应是有益的。
这是另一种“视情况而定”的情况。但下次有人要你做一个 Promise
时,考虑偷懒吧 (͡° ͜ʖ ͡°)。