首页 > 解决方案 > 在 Promise 中编写同步代码的正确方法是什么?

问题描述

几天前,我在 YouTube 上观看了一段关于James Snell介绍的Broken Promises的有趣视频。

您可以从他的资料库中找到视频中介绍的一些很好的示例。

按照他的说法,我们不应该将纯粹的同步代码包装在 Promise 中。如果我们绝对需要一个函数来返回一个 Promise,那么使用Promise.resolve() method. 重要的是,他还指出同步运行您的代码并省去所有这些额外的承诺分配的麻烦

看完视频后,我查看了我的代码。并且想知道如果我做了与他在剪辑中提到的类似的事情会怎样。

让我给你看一些例子。

这是一个等待 promise 被解决的异步函数。

async getGameShotDetail(buffer: Buffer, fileSize: number): Promise<ShotDetail[]> {
  const { latitude, longitude, shotType } = await somefunc()
  const data = await Promise.all([
    this.parseHoleNumber(shotType),
    this.parseShotType(shotType),
    this.parseCoordinate(this.sliceBufferIntoPieces(latitude)),
    this.parseCoordinate(this.sliceBufferIntoPieces(longitude)),
  ]);

  return some async func(data);
}

前两个方法parseHoleNumber和方法parseShotType数组内部的Promise.all()作用几乎相同。它从二进制文件中读取数据,它们最终都返回一个数字数组作为承诺。

private parseHoleNumber(buffer: number[]): Promise<number[]> {
  return new Promise((resolve, reject) => {
    if (buffer.length < 0) {
      reject([]);
    }
    /* tslint:disable:no-bitwise */
    resolve(buffer.filter(n => n !== 0).map(holeNumber => holeNumber >> 3));
    /* tslint:enable:no-bitwise */
  });
}

我在这里要完成的是编写返回承诺的同步函数。问题是我不确定我的代码是否像 James Snell 所说的那样写得很好。

据我所知,包括 push 在内的所有Array方法都是同步的。而且我不确定将项目推送到数组中new Promise(executor)是否安全。

我的代码中是否存在任何易受攻击或错误使用的承诺?

private sliceBufferIntoPieces(
  buffer: number[] | string[],
  chunkSize: number = 4,
): Promise<Array<number[]>> {
  const arr = [];

  return new Promise((resolve, reject) => {
    for (let i = 0; i < buffer.length; i += chunkSize) {
      arr.push(buffer.slice(i, i + chunkSize));
    }
    arr.length > 0 ? resolve(arr) : reject([]);
  });
}

private async parseCoordinate(buffer: Promise<Array<number[]>>): Promise<number[]> {
  const itemsAreZero = (item): boolean => item === 0;

  return Promise.resolve(
    (await buffer) // Maybe this is bad?
      .filter(buff => !buff.every(itemsAreZero))
      .map(byte => +(this.read4byteItem(byte) / 360000).toFixed(6)),
  );
}

我要亲自感谢詹姆斯的精彩演讲

标签: javascriptnode.jstypescriptpromise

解决方案


您的代码中的所有函数都没有异步执行任何操作。一旦await somefunc线路运行,您所做的一切都是同步的,但Promise.all由于某种原因,您仍然将所有内容包装在调用中。Promise.resolve您可以通过删除不必要的 Promise 结构和s来解决这个问题(并避免视频讨论的反模式) :

async getGameShotDetail(buffer: Buffer, fileSize: number): Promise<ShotDetail[]> {
  const { latitude, longitude, shotType } = await somefunc()
  const data = [
    this.parseHoleNumber(shotType),
    this.parseShotType(shotType),
    this.parseCoordinate(this.sliceBufferIntoPieces(latitude)),
    this.parseCoordinate(this.sliceBufferIntoPieces(longitude)),
  ];

  return some async func(data);
}
private parseHoleNumber(buffer: number[]): number[] {
    if (buffer.length < 0) {
      // If you don't want processing to continue in getGameShotDetail, throw an error:
      throw new Error('Buffer length negative??');
      // Otherwise, just return an empty array:
      // return [];
    }
    /* tslint:disable:no-bitwise */
    return buffer.filter(n => n !== 0).map(holeNumber => holeNumber >> 3);
    /* tslint:enable:no-bitwise */
}
private sliceBufferIntoPieces(
  buffer: number[] | string[],
  chunkSize: number = 4,
): Array<number[]> {
  const arr = [];

    for (let i = 0; i < buffer.length; i += chunkSize) {
      arr.push(buffer.slice(i, i + chunkSize));
    }
    if (arr.length === 0) {
      // Same as above - do you want to return an empty array, or stop execution entirely?
      throw new Error('Buffer empty');
    }
}

关于上述两个函数,如果缓冲区为空,请考虑 - 您真的要完全停止执行,还是要继续执行空数组?如果你想停止执行,抛出一个错误(用throw) - 否则,不要抛出,只返回一个空数组。

由于sliceBufferIntoPieces不需要返回 Promise,因此也不需要parseCoordinate等待它解决:

private parseCoordinate(buffer: Array<number[]>): number[] {
  const itemsAreZero = (item): boolean => item === 0;

  return buffer
      .filter(buff => !buff.every(itemsAreZero))
      .map(byte => +(this.read4byteItem(byte) / 360000).toFixed(6))
}

还要记住,Typescript 几乎总是可以推断出函数返回值的类型,而无需您显式指定它 - 除非您的 linter 迫使您注意返回类型,否则请随意忽略这些类型。


推荐阅读