在promise中编写同步代码的正确方法是什么?

时间:2019-12-30 05:57:58

标签: javascript node.js typescript promise

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

您可以从他的repository的视频中找到一些很好的例子。

  

根据他所说,我们不应在承诺中包装纯粹的同步代码。而且,如果我们绝对需要一个函数来返回承诺,则可以使用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方法数组中的前两个方法parseShotTypePromise.all()几乎是同一件事。它从二进制文件中读取数据,并且它们最终都返回一个数字数组作为Promise。

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所说的代码是否写得很好。

据我所知,所有Array方法(包括push)都是同步的。而且我不确定将项目从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)),
  );
}

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

1 个答案:

答案 0 :(得分:2)

您的代码中没有任何函数可以异步执行任何操作。 await somefunc行运行之后,您正在执行的所有其他操作都是同步的,但是由于某些原因,您仍将所有内容包装在Promise.all调用中。您可以通过删除不必要的Promise构造和Promise.resolve 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[]) {
    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无需返回承诺,因此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几乎总是可以推断出函数返回值的类型,而无需您显式指定它-除非您的后代强迫您注意返回类型,否则请不要理会那些返回值。