为 JavaScript 中的每个用户同步 API 中的关键部分

Synchronize critical section in API for each user in JavaScript

我想交换用户的头像。为此,我必须检查数据库,看是否有图片已保存,如果有,则应将其删除。然后将新的保存并输入数据库。

这是一个简化(伪)代码

async function changePic(user, file) {
  // remove old pic
  if (await database.hasPic(user)) {
    let oldPath = await database.getPicOfUser(user);
    filesystem.remove(oldPath);
  }
  // save new pic
  let path = "some/new/generated/path.png";
  file = await Image.modify(file);
  await Promise.all([
   filesystem.save(path, file),
   database.saveThatUserHasNewPic(user, path)
  ]);
  return "I'm done!";
}

我运行用它解决了下面的问题
如果用户在短时间内两次调用API,就会出现严重的错误。数据库查询和两者之间的函数是异步的,导致当第二个 API 检查要删除的个人资料图片时,第一个 API 调用的更改未应用。所以我留下了 filesystem.remove 请求文件系统中已经不存在的文件和未删除的图像。

我想通过同步代码的关键部分来安全地处理这种情况。我不想仅仅因为服务器还没有完成前一个请求就拒绝请求而且我还想为每个用户同步它,这样用户就不会被其他用户的行为打扰。

在 JavaScript 中是否有一种干净的方法来实现这一点?像您从 Java 知道的那样的某种 monitor 会很好。

您可以使用 p-limit 这样的库来控制并发。使用地图跟踪每个用户的 active/pending 请求。使用他们的 ID(我假设存在)作为键和限制实例作为值:

const pLimit = require('p-limit');

const limits = new Map();

function changePic(user, file) {
  async function impl(user, file) {
    // your implementation from above
  }

  const { id } = user // or similar to distinguish them

  if (!limits.has(id)) {
    limits.set(id, pLimit(1)); // only one active request per user
  }

  const limit = limits.get(id);
  return limit(impl, user, file); // schedule impl for execution
}

// TODO clean up limits to prevent memory leak?