Что такое канонически безопасный способ асинхронной ленивой инициализации в javascript? - PullRequest
5 голосов
/ 11 ноября 2019

У меня в программе такой ленивый код инициализации:

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() будет вызываться два раза вместо одного.

Как избежать этой ситуации?

1 Ответ

4 голосов
/ 11 ноября 2019

При первом вызове вместо этого назначьте Обещание, а при дальнейших вызовах верните это Обещание:

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');
})();

Но не было бы, если Обещания были инициализированы параллельно, до того, как одно из них было await подготовлено к завершению первым:

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 для постоянной переменной (вместо await значение внутри getUser) исправляет это.

...