【问题标题】:Parse.Cloud.afterSave not working with parse-serverParse.Cloud.afterSave 无法与解析服务器一起使用
【发布时间】:2017-03-30 17:01:44
【问题描述】:

我正在使用 parse.com 并编写了一个完美运行的云代码功能。当我转移到一个自托管的解析服务器后端时,一些云代码功能停止工作。

Parse.Cloud.afterSave("League", function (request) {

    if (request.object.get("leaderboard") == null) {

        var leaderboard = Parse.Object.extend("Leaderboard");
        var newInstance = new leaderboard();

        newInstance.save(null , {useMasterKey: true})
            .then(function (result) {
                request.object.set("leaderboard", result);
                request.object.save(null ,{useMasterKey: true});
            },
            function (error) {
                console.log("Error");
            });
        });
    }else{
        var membersRelation = request.object.relation("members");
        var membersQuery = membersRelation.query();
        membersQuery.count(null , {useMasterKey: true})
            .then(function (totalNumber) {
                request.object.set("memberCount", totalNumber)
                request.object.save(null ,{useMasterKey: true});
            }, function (error) {
                console.log("Error")
            })
     }

如您所见,我为League 类定义了afterSave 挂钩。在我的钩子中,当我设置一个新值(排行榜和/或 membersCount)时,我必须再次更新同一个对象,因此保存不止一次被称为保存。

该函数可以正确保存数据,但也会导致无限循环。我知道这是因为我调用request.object.save() 会再次更改League 类,所以afterSave 事件会再次触发,依此类推。我不知道我该如何处理这种情况。有人建议我添加超时但不知道如何。你能帮忙解决这个问题吗?

谢谢

【问题讨论】:

  • 如果request.object.has('leaderboard') 返回true,则不做任何工作直接返回。这样你就可以防止无限循环。
  • 嗨 cYrixmorten 感谢您的评论。我已经更新了我的代码,我需要检查其他条件,然后需要返回。
  • 没问题 :) 是否按预期工作?

标签: javascript parse-platform parse-server parse-cloud-code


【解决方案1】:

你的方法有两个问题:

  1. leaderboard 存在竞争条件。当第一次保存的承诺解决时,不会有leaderboard,然后它会神奇地出现在“未来的某个时候”。在beforeSave 中设置初始值要好得多,这样league 的状态是已知且可预测的。

  2. membersCount 上还有一个竞争条件。想象一下,添加和/或删除members 的两个更新同时出现。在读取关系和写入计数之间,可能会发生其他更新。您最终可能会得到错误的计数,甚至是负数!

要解决 1,我们只需将 leaderboard 的创建移动到 beforeSave 中。为了解决2,我们将membersCount的计算移到beforeSave中,使用提供的关于member加减的脏对象信息,最后我们使用increment来确保更新是原子的并避免竞争条件。

下面是带有单元测试的工作代码。请注意,如果我对此进行自己的代码审查,我会 a) 想要测试添加多个和减去多个成员 b) 将大的第一个测试拆分为多个测试,每个测试只测试一个东西。 c) 在同一个保存中测试添加和删除。

我正在使用 es6 构造,因为我喜欢它们;)。

尝试放入很多 cmets,但如果有什么令人困惑的地方,请随时问我。

PS 如果您不知道如何在云代码上执行和运行单元测试,请再问一个问题,因为它对于弄清楚这些东西是如何工作的非常宝贵(并且查看解析服务器单元测试是最好的文档)

祝你好运!

const addLeaderboard = function addLeaderboard(league) {
  // note the simplified object creation without using extends.
  return new Parse.Object('Leaderboard')
    // I was surprised to find that I had to save the new leaderboard
    // before saving the league. too bad & unit tests ftw.
    .save(null, { useMasterKey: true })
    // "fat arrow" function declaration.  If there's only a single
    // line in the function and you don't use {} then the result
    // of that line is the return value.  cool!
    .then(leaderboard => league.set('leaderboard', leaderboard));
}

const leagueBeforeSave = function leagueBeforeSave(request, response) {
  // Always prefer immutability to avoid bugs!
  const league = request.object;

  if (league.op('members')) {
    // Using a debugger to see what is available on the league
    // is super helpful, cause I have never seen this stuff
    // documented, but its obvious in a debugger.
    const membersAdded = league.op('members').relationsToAdd.length;
    const membersRemoved = league.op('members').relationsToRemove.length;
    const membersChange = membersAdded - membersRemoved;
    if (membersChange !== 0) {
      // by setting increment when the save is done, the
      // change in this value will be atomic.  By using a change
      // in the value rather than an absolute number
      // we avoid a race condition when paired with the atomicity of increment
      league.increment('membersCount', membersChange);
    }
  }

  if (!league.get('leaderboard')) {
    // notice we don't have to save the league, we just
    // add the leaderboard.  When we call success, the league
    // will be saved and the leaderboard will be there....
    addLeaderboard(league)
      .then(() => response.success(league))
      .catch(response.error);
  } else {
    response.success(league);
  }
};

// The rest of this is just to test our beforeSave hook.
describe('league save logic', () => {

  beforeEach(() => {
    Parse.Cloud.beforeSave('League', leagueBeforeSave);
  });

  it('should create league and increment properly', (done) => {
    Parse.Promise.when([
      new Parse.Object('Member').save(),
      new Parse.Object('Member').save(),
      new Parse.Object('Member').save(),
      new Parse.Object('Member').save(),
    ])
      .then((members) => {
        const league = new Parse.Object('League');
        const memberRelation = league.relation('members');
        memberRelation.add(members);
        // I want to use members in the next promise block,
        // there are a number of ways to do this, but I like
        // passing the value this way.  See Parse.Promise.when
        // doc if this is mysterious.
        return Parse.Promise.when(
          league.save(null, { useMasterKey: true }),
          members);
      })
      .then((league, members) => {
        expect(league.get('leaderboard').className).toBe('Leaderboard');
        expect(league.get('membersCount')).toBe(4);
        const memberRelation = league.relation('members');
        memberRelation.remove(members[0]);
        return league.save(null, { useMasterKey: true });
      })
      .then((league) => {
        expect(league.get('membersCount')).toBe(3);
        // just do a save with no change to members to make sure
        // we don't have something that breaks in that case...
        return league
          .set('foo', 'bar')
          .save(null, { useMasterKey: true })
      })
      .then(league => {
        expect(league.get('foo')).toBe('bar');
        done();
      })
      .catch(done.fail);
  });

  it('should work to create new without any members too', (done) => {
    new Parse.Object('League')
      .save() // we don't really need the useMasterKey in unit tests unless we setup `acl`s..:).
      .then((league) => {
        expect(league.get('leaderboard').className).toBe('Leaderboard');
        done();
      })
      .catch(done.fail);
  });
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    • 1970-01-01
    • 1970-01-01
    • 2022-11-05
    • 2022-10-19
    • 2015-09-10
    相关资源
    最近更新 更多