【问题标题】:What is a canonical safe way of async lazy initialization in javascript?javascript中异步延迟初始化的规范安全方法是什么?
【发布时间】:2019-11-11 17:44:40
【问题描述】:

我的程序中有这种惰性初始化代码:

let user = null;        
let getUser = async () => {
  if(!user) {
    user = await getUserSomehow();
  }
  return user;
};

我知道这是不安全的,因为如果我有下一个代码可能会出现竞争情况:

// one place of the program
let u1 = await getUser();
...
// another place of the program running during getUserSomehow() for u1 still hasn't finished
let u2 = await getUser();

getUserSomehow() 将被调用两次而不是一次。

如何避免这种情况?

【问题讨论】:

  • 只需删除await
  • @Bergi 你的评论太短了,乍一看它看起来很愚蠢(就像你建议摆脱异步代码一样)。但是,如果不打算在任何地方直接使用“用户”变量,那么您是完全正确的。

标签: javascript asynchronous async-await eslint


【解决方案1】:

第一次调用时,分配一个 Promise,而在进一步调用时,返回该 Promise:

let userProm = null;
let getUser = () => {
  if (!userProm) {
    userProm = getUserSomehow();
  }
  return userProm;
};

更好的是,将userProm 的范围仅限于getUser,这样更安全、更清晰:

const getUser = (() => {
  let userProm = null;
  return () => {
    if (!userProm) {
      userProm = getUserSomehow();
    }
    return userProm;
  };
})();

const getUserSomehow = () => {
  console.log('getting user');
  return Promise.resolve('data');
};

const getUser = (() => {
  let userProm = null;
  return () => {
    if (!userProm) {
      userProm = getUserSomehow();
    }
    return userProm;
  };
})();

(async () => {
  const userProm1 = getUser();
  const userProm2 = getUser();
  Promise.all([userProm1, userProm2]).then(() => {
    console.log('All done');
  });
})();

您现有的代码碰巧是安全的,因为对user 的分配将在getUser 的第一次调用完成之前发生:

const getUserSomehow = () => {
  console.log('Getting user');
  return Promise.resolve('data');
};

let user = null;
let getUser = async() => {
  if (!user) {
    user = await getUserSomehow();
  }
  return user;
};

(async () => {
  let u1 = await getUser();
  let u2 = await getUser();
  console.log('Done');
})();

但如果 Promise 是在其中一个被 awaited 完成之前并行初始化的,则不会:

const getUserSomehow = () => {
  console.log('Getting user');
  return Promise.resolve('data');
};

let user = null;
let getUser = async() => {
  if (!user) {
    user = await getUserSomehow();
  }
  return user;
};

(async() => {
  let u1Prom = getUser();
  let u2Prom = getUser();
  await Promise.all([u1Prom, u2Prom]);
  console.log('Done');
})();

如上所示,将 Promise 分配给持久变量(而不是 awaiting getUser 中的值)可以解决此问题。

【讨论】:

  • 感谢提供更好理解的几个示例!你是对的,在我原来的帖子中,我有一个关于获取比赛条件的错误示例。
  • 回答得很好。我很惊讶这个问题没有更频繁地出现。我遵循的另一种做法是将我的 promise 引用保留在模块的顶部(以便我可以跟踪它们)并为每个实现一个“getter”和“resetter”。内部作用域的一个缺点是:无法编写“重置器”。
猜你喜欢
  • 2015-10-17
  • 1970-01-01
  • 1970-01-01
  • 2012-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-23
  • 2012-07-27
相关资源
最近更新 更多