我编写的大多数 JavaScript 代码都严重依赖于 Promise 和 async/await。我遇到的一个问题是,我希望为我的库的用户提供一个流畅的接口,同时使用 async/await 来实现。
举个例子,考虑下面这个函数:
async function getArticles() {
const response = await fetch('https://api.example.org');
const json = await response.json();
return json;
}
这个函数的用户可能会这样调用它:
const articles = await getArticles();
我想让用户能够为此调用添加自定义的 HTTP 头部。一种简单的方法是在getArticles()函数中添加一个headers参数作为选项。在大多数情况下,这可能是正确的选择。
但在我的情况下,HTTP 头部并不是唯一可能需要的可选参数,而且我不想让接口变得极其复杂。我真正想要的是一个流畅的接口,特别是能够以以下两种方式调用函数:
const articles = await getArticles();
const articles = await getArticles().withHeader('X-Foo', 'Bar');
我需要弄清楚如何从那个函数返回一个既像 Promise 又可以添加自定义函数的东西。事实证明,对象不需要是全局的Promise对象就可以与 Promise 或 async/await 一起使用。它们所需要的只是一个行为与 Promise 的then函数相同的then函数。
我最终得到了这个解决方案:
class HeaderPromise {
constructor(uri) {
this.uri = uri;
this.headers = {};
}
withHeader(key, value) {
this.headers[key] = value;
return this;
}
then(onResolved, onRejected) {
return this.getInnerPromise().then(onResolved, onRejected);
}
getInnerPromise() {
if (!this.innerPromise) {
this.innerPromise = (async () => {
const response = await fetch(this.uri, { headers: this.headers });
const json = await response.text();
return json;
})();
}
return this.innerPromise;
}
}
function getArticles() {
return new HeaderPromise('https://api.example/');
}
我所做的更改是getArticles不再是async函数,但它返回一个await会视为Promise的对象。
让我们来看看每个类函数:
constructor(uri) {
this.uri = uri;
this.headers = {};
}
我们需要将所有相关信息传递给构造函数。这意味着如果你想提供这样的 API,对于每个返回流畅 Promise 接口的 API 函数,可能都需要一个自定义类。
withHeader(key, value) {
this.headers[key] = value;
return this;
}
withHeader更新头部列表,为了确保流畅性,它总是返回自身。这允许这个函数被多次调用,并最终允许等待类似 Promise 的对象。
then(onResolved, onRejected) {
return this.getInnerPromise().then(onResolved, onRejected);
}
then函数完成所有“Promise 相关的工作”。它实际上只是将其功能委托给一个真正的 Promise,而不是实现所有的 Promise 逻辑(这很难!)。
getInnerPromise() {
if (!this.innerPromise) {
this.innerPromise = (async () => {
const response = await fetch(this.uri, { headers: this.headers });
const json = await response.text();
return json;
})();
}
return this.innerPromise;
}
getInnerPromise是完成所有工作的函数。一个重要的细微差别是,如果它被多次调用,它只会执行一次工作。这很重要,因为如果你对一个 Promise 调用then两次,它不应该执行两次工作。在 Promise 完成工作后,它应该存储结果并在以后调用then时返回它。
总之,就是这样!这很复杂,我的一般建议是除非别无他法,否则应避免使用这种模式。使用这种模式会使你的实现不太易读,但潜在的好处是有一个更好的接口。接口比实现更重要,但要注意不要使维护变得太难。
最后,这是 TypeScript 的等效代码:
class HeaderPromise<T> implements PromiseLike<T> {
uri: string;
headers: { [key: string]: string };
innerPromise: Promise<T>;
constructor(uri: string) {
this.uri = uri;
this.headers = {};
}
withHeader(key: string, value: string): this {
this.headers[key] = value;
return this;
}
then<TResult1 = any, TResult2 = never>(onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2> {
return this.getInnerPromise().then(onfulfilled, onrejected);
}
getInnerPromise() {
if (!this.innerPromise) {
this.innerPromise = (async () => {
const response = await fetch(this.uri, { headers: this.headers });
const json = await response.json();
return json;
})();
}
return this.innerPromise;
}
}
function getArticles(): HeaderPromise<any> {
return new HeaderPromise('https://api.example/');
}