paint-brush
关于 Promises、Thenables 和惰性求值你需要知道的一切经过@austingil
1,653 讀數
1,653 讀數

关于 Promises、Thenables 和惰性求值你需要知道的一切

经过 Austin Gil5m2023/01/20
Read on Terminal Reader

太長; 讀書

在新的一年里,我将向您展示如何让 `Promise` 变得更懒惰。让我们看一个基本的“Promise”示例。在这里,我有一个名为 sleep 的函数,它需要以毫秒为单位的时间和一个值。它返回一个承诺,该承诺将在我们应该等待的毫秒数内执行 `setTimeout`。
featured image - 关于 Promises、Thenables 和惰性求值你需要知道的一切
Austin Gil HackerNoon profile picture

这是新的一年的开始,虽然很多人都承诺要更加活跃,但我将向您展示如何让Promise变得更懒惰…… JavaScript 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 控制台。然后一秒钟后,“'打哈欠和伸展'”


我们可以使用参数1000'Yawn & stretch'等待sleep函数,一秒钟后, console将记录字符串 'Yawn & stretch'。


这没什么特别的。它的行为可能与您预期的一样,但是如果我们将它存储为变量以await稍后使用,而不是立即await返回的Promise ,它会变得有点奇怪。


 const nap = sleep(1000, 'Yawn & stretch')


现在,假设我们做一些其他需要时间的工作(比如输入下一个示例),然后await nap变量。


在 JavaScript 控制台中输入“await nap”并立即看到响应“'Yawn & stretch'”

您可能希望在解析之前延迟一秒钟,但实际上它会立即解析。任何时候你创建一个Promise ,你实例化它负责的任何异步功能。


在我们的示例中,当我们定义nap变量时,就会创建执行setTimeoutPromise 。因为我打字慢,所以Promise会在我们await它的时候解决。


换句话说, Promise是渴望的。他们不会等你await他们。


在某些情况下,这是一件好事。在其他情况下,它可能会导致不必要的资源使用。对于这些场景,您可能想要一些看起来像Promise的东西,但使用惰性评价仅在需要时实例化。


在我们继续之前,我想向您展示一些有趣的东西。


Promise并不是 JavaScript 中唯一可以await的东西。如果我们使用.then()方法创建一个普通Object ,我们实际上可以像任何Promise一样await该对象。


JavaScript 控制台显示文本“await { then: () => console.log('🙃') }”,后跟“🙃”。

这有点奇怪,但它也允许我们创建看起来Promise的不同对象,但实际上不是。这些对象有时被称为“然后能“。


考虑到这一点,让我们创建一个新的班级称为LazyPromise ,它扩展了内置的Promise构造函数。扩展 Promise 并不是绝对必要的,但它使它看起来更类似于使用instanceof之类的Promise


 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 ,它都有效。


现在,让我们看看如果将原始sleep函数中的Promise替换为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时,考虑一下偷懒 (͡° ͜ʖ ͡°)。


非常感谢您的阅读。如果您喜欢这篇文章,请分享它.这是支持我的最好方式之一。你也可以注册我的时事通讯或者在推特上关注我如果您想知道新文章何时发布。


最初发表于austingil.com .