如何改进此 Promise 链接并获得相同的行为

How to improve this Promise chaining and obtain same behaviour

我需要确保 prepare 函数在继续执行之前获取所有值,这就是我链接承诺的原因。

此代码有效,但我需要为很多不同的值执行此操作,并且代码将变得一团糟,因为链接实际上具有嵌套(在 then 中调用 getValue 并使用 thengetValue 等等)

那么我怎样才能通过更好的做法或更清洁的方法获得相同的结果

import { BackendService } from "../../core/backend.service";

export class Configuration {
  assetsBucketAccessKeyId: string ;
  assetsBucketSecretAccessKey: string ;
}

export namespace ConfigurationService {
  const configurations: { [key: string]: string } = {};
  const configurationURL = "configuration";
  export const values = new Configuration() ;

  export function prepare(backendService: BackendService) {
    ConfigurationService
      .getValue("assetsBucketAccessKeyId", backendService)
      .then(v1 => {values.assetsBucketAccessKeyId = v1; } )
      .then(() => { ConfigurationService
        .getValue("assetsBucketSecretAccessKey", backendService)
        .then(v2 => {values.assetsBucketSecretAccessKey = v2; } ); }) ;
  }

 export function getValue(
    keyName: string,
    backendService: BackendService
  ): Promise<string> {
    let resp: string;

    resp = null;

    return new Promise((resolve, reject) => {
      // find or query te value
      if (!configurations[keyName]) {
        const requestURL = `${configurationURL}/${keyName}`;

        backendService.get<string>(requestURL).subscribe(
          value => {
            configurations[keyName] = value;
            resolve(value);
          },
          error => {
            console.log(error);
            reject(null);
          }
        );
      } else {
        resolve(configurations[keyName]);
      }
    });
  }
}

您可以使用 javaScripts async/await 语法来处理承诺,例如

export async function prepare(backendService: BackendService) {
    values.assetsBucketAccessKeyId = await ConfigurationService.getValue("assetsBucketAccessKeyId", backendService);
    values.assetsBucketSecretAccessKey = await ConfigurationService.getValue("assetsBucketSecretAccessKey", backendService);
}

这种方法在使用 promises 时更易于阅读代码。

您可以通过多种方式调用此函数。

1) 使用 Promise 代码调用它,例如

prepare(backendService).then(() => { // assetsBucketAccessKeyId/assetsBucketSecretAccessKey will now be available})

2) 从另一个异步方法调用它,例如

async function wrapper() {
 await prepare(backendService)
 // assetsBucketAccessKeyId/assetsBucketSecretAccessKey now available to access...
}

3) 从匿名函数中调用它,例如

(async () => {
  await prepare(backendService)
  // assetsBucketAccessKeyId/assetsBucketSecretAccessKey now available to access...
})();

由于目标函数已经被 promified,你可以使用 async await ES6 语法。

示例:

export async function prepare(backendService: BackendService) {
    values.assetsBucketAccessKeyId = await ConfigurationService.getValue(
          'assetsBucketAccessKeyId',
          backendService
        );
    values.assetsBucketSecretAccessKey = await ConfigurationService.getValue(
          'assetsBucketSecretAccessKey',
          backendService
       );
}

别忘了在函数名前加async,在函数调用前加await

Note: 如果您的任何函数没有返回承诺,那么这将不起作用。

只要您预先知道所有密钥,就可以 运行 通过系列中的承诺。系列逻辑非常粗糙,但它基本上是一种实用方法,可以在任何地方用于此目的。

下面的更改是所有更改,您的代码中的其他所有内容均未更改。

您可能需要稍微重构您的代码以处理将上下文传递给序列化解析的回调,但这很容易。

我使用的是 es7,但如果你愿意,可以转换。

如果您需要更多解释,请告诉我。

export async function serializedRun<T>(promiseFunctions: (() => Promise<T>)[], callback: (T) => void) {
  // se below for reasoning of the null \/ here
    return await [...promiseFunctions, null ].reduce(async (prevPromise, nextPromise, index) => {
        const result = await prevPromise;

    // Skip initalValue promise
    if (index === 0) {
      // Do something cool with each result
      callback(result)
    }else if (nextPromise !== null) { // Skip final value
      // you need a another cycle beyond the last promise to await the last returned promise
            return nextPromise();
        } else {
            return Promise.resolve(result); // Final resolved value
        }
    }, Promise.resolve()); // ininitalValue promise to start the cycle
}

export namespace ConfigurationService {
  const configurations: { [key: string]: string } = {};
  const configurationURL = "configuration";
  const keyNames = [
    'assetsBucketAccessKeyId',
    'assetsBucketSecretAccessKey',
  ]

  export const values = new Configuration() ;

  export async function prepare(backendService: BackendService) {
    await serializedRun(
      // This looks crazy below but you don't want to evoke the promise yet
      // So you need to return a function that the serializedRun method can invoke
      keyNames.map((key) => () => ConfigurationService.getValue(key, backendService)),
      (value) => {
        // You need to figure out a way pass the required params to this funciton
        // for it to know what to do...
        if (/* something */) {
          values.assetsBucketAccessKeyId = value;
        } else {
          values.assetsBucketSecretAccessKey = value;
        }
      })
  }

NOTE: You would also need to handle any errors in the serializedRun method with try/catch blocks