【问题标题】:How to await for connection before resolving other Promises?如何在解决其他承诺之前等待连接?
【发布时间】:2022-11-21 18:50:47
【问题描述】:

我发现了一个有趣的问题(不是真正的任务):

class Client {
  async connect() {
      return new Promise(resolve => {
          setTimeout(() => {
              console.log('connect');
              resolve()
          }, 2000);
      })
  }

  async request() {
      console.log('done something')
  }
}

class Api {
  #client;

  async getClient() {
      if (!this.#client) {
          const client = new Client();
          await client.connect();
          this.#client = client;
      }
      return this.#client;
  }

  async request() {
      const client = await this.getClient();
      return client.request();
  }
}

const run = async () => {
  const api = new Api();

  await Promise.all([api.request(), api.request(), api.request(), api.request()]);
}

run();

它会输出这个:

connect
request completed
connect
request completed
connect
request completed
connect
request completed

显然,我们需要等待初始连接,然后再解决其他请求,所以输出将是这样的:

connect
request completed
request completed
request completed
request completed

我的工作解决方案:

class Client {
    async connect() {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log('connect');
                resolve()
            }, 2000);
        })
    }

    async request() {
        console.log('request completed')
    }
}

class Api {
    #connecting = false;
    #client;
    #queue = [];

    async getClient() {
        if (!this.#client && !this.#connecting) {
            this.#connecting = true;
            const client = new Client();
            await client.connect();

            this.#connecting = false;
            this.#client = client;

            this.resolveQueue(client)
        }
        return this.#client;
    }

    async resolveQueue(client) {
        await Promise.all(this.#queue.map(task => task(client)))
    }

    async request() {
        const client = await this.getClient();

        if (client) {
            return client.request();       
        } else {
            this.#queue.push(async (client) => client.request());
        }

    }
}

const run = async () => {
    const api = new Api();

    await Promise.all([api.request(), api.request(), api.request(), api.request()]);
}

run();

但似乎应该有更清晰/更简单的方法来做到这一点,因为我增加了很多层次和复杂性。另外,我认为这里的目标是添加尽可能少的更改,保持代码结构不变,我也试图实现这一点。在现实世界的情况下,我可能会完全重写它并使其更加冗长。

【问题讨论】:

    标签: javascript promise


    【解决方案1】:

    对此有多种不同的方法。通过对代码进行最少的更改,您需要做两件事:

    1. 记住Client中对connect结果的承诺,这样你就可以等待它在request中实现
    2. Api中,设置this.#client立即地,在等待连接结果之前,这样对request的多次调用不会创建多个单独的Client实例

      class Client {
          // Have a promise for connection, which also gives you a handy way
          // to know that `connect` has been called
          connectionPromise = null;
          async connect() {
              // Remember the connection promise
              this.connectionPromise = new Promise((resolve) => {
                  setTimeout(() => {
                      console.log("connect");
                      resolve();
                  }, 200);
              });
              return this.connectionPromise;
          }
      
          async request() {
              // Check that `connect` has been called
              if (!this.connectionPromise) {
                  throw new Error(`Can't call 'request' before calling 'connect'`);
              }
              // Wait until the promise is fulfilled
              await this.connectionPromise;
              // Now do the request
              console.log("done something");
          }
      }
      
      class Api {
          #client;
      
          async getClient() {
              if (!this.#client) {
                  // Set `this.#client` immediately, before awaiting
                  // the connection, so multiple overlapping calls to
                  // `request` use the **same** client
                  this.#client = new Client();
                  await this.#client.connect();
              }
              return this.#client;
          }
      
          async request() {
              const client = await this.getClient();
              return client.request();
          }
      }
      
      const run = async () => {
          const api = new Api();
      
          await Promise.all([api.request(), api.request(), api.request(), api.request()]);
      };
      
      run();
      .as-console-wrapper {
          max-height: 100% !important;
      }

      ,我建议您将Client 分成两部分:

      • 进行连接的部分(它可以只是一个函数;如果你愿意,它可以成为 Client 上的 static 方法)
      • 可以做请求的客户端对象

      这样,您就永远不会有一个未准备好使用的有状态 Client 实例。当你得到一个(来自连接的功能)时,它已经连接并且可以使用了。

    【讨论】:

      猜你喜欢
      • 2020-04-22
      • 2018-12-14
      • 2018-03-19
      • 2017-04-02
      • 1970-01-01
      • 1970-01-01
      • 2020-08-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多