关于Promise应用的一些思考

285 阅读4分钟

1.webp

起因

这次事件的出发点起源于一个关于优化网络请求的问题,我先把问题放在下面看各位能不能做出来。

let _requestTime = 0;
// 模拟网络请求
const requestUserInfo = () => {
  // 这个方法的实现不能修改
  return Promise.resolve().then(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        // 模拟 ajax 异步,1s 返回
        resolve();
      }, 1000);
    }).then(() => {
      // 当执行完成一次之后次数加1
      _requestTime++;
      return {
        nick: "nick",
        age: "18",
      };
    });
  });
};

// -------- 在这里完成代码 优化getUserInfo --------
// 调用 requestUserInfo,并优化请求次数
const getUserInfo = async () => {
  
};

// 业务场景 -- 页面中的组件都用到了requestUserInfo请求, 想办法优化请求把请求次数降为1次
async function test () {
  const result = await Promise.all([
    getUserInfo(),
    new Promise((resolve) =>
      setTimeout(async () => {
        resolve(await getUserInfo());
      }, 300)
    ),
    new Promise((resolve) =>
      setTimeout(async () => {
        resolve(await getUserInfo());
      }, 2300)
    ),
  ]);
  console.log(_requestTime, result) // 保证执行完Promise.all之后_requestTime为1就算成功
}

test()

自己的思考

在公布正确答案之前首先说一下我自己的思考逻辑,首先请求既然只能执行一次,那么肯定是需要进行缓存的,这样下一次请求的时候如果有缓存就可以拿缓存就可以不走请求的逻辑,那么初步代码就完成了。

let cache = null
const getUserInfo = async () => {
  if (cache) return cache
  let userInfo = await requestUserInfo()
  cache = userInfo
  return cache
};

但是这样只能把请求次数降为2次,原因也很简单,当第二次请求的时候因为第一次请求还没有被完全执行,所以第二次请求的时候还没有缓存,只有当第三次请求的时候cache才会被赋值成功,所以只有第三次函数执行的时候是没有请求的。

请求等待

所以说现在的问题就是解决当第二次请求的时候让它等待,等待第一次请求完成之后cache有值之后才能够继续下去,下面是完善后的代码。

let cache = null
let loading = false
const getUserInfo = async () => {
  while (loading) {}  
  if (cache) return cache
  if (_requestTime === 0) loading = true
  let userInfo = await requestUserInfo()
  loading = false
  cache = userInfo
  return cache
};

如上述代码所示,我用了一个loading变量用来控制是否第一次请求是否正在执行,当第一个函数执行开始时把loading变成true,然后等待第一个函数执行完成之后把loading变成false,这样的话while循环也会解除,后续返回的都是cache,这样就做到了后续的函数执行都必须等到第一次函数执行cache被赋值之后才能进行下去,保证后续函数返回的都是cache。

但是,理想很丰满,现实很骨感,只能说现实不会如你所愿的执行下去,有时候程序也一样,当我执行完上述的函数之后,发现会一直卡死在while循环那里,原因不得而知,各位大佬要是知道的话一定要告诉我求求了。

问题解决

因为这边我实在是没啥办法了,所以我只能求助于江湖的各位大佬,最终我的问题在大佬的帮助下得到了解决,只能说大佬不愧是大佬,只用一行代码就解决了困扰了我许久的问题。

const getUserInfo = () => {
  return getUserInfo.promise ||= requestUserInfo();
};

上述代码的功能就是把requestUserInfo这个函数返回的Promise对象缓存起来,每一次都是返回的同一个Promise对象,这样的话就只执行了一次。

原因分析

要搞懂上面的代码,你必须先要了解Promise,首先要知道then函数返回的是一个Promise对象,这样我们就可以把问题简化,我们只需要看看Promise.all中的对象是否是同一个Promise对象即可。

new Promise((resolve) => {
  setTimeout(() => {
    // 模拟 ajax 异步,1s 返回
    resolve();
  }, 1000);
}).then(() => {
  // 当执行完成一次之后次数加1
  _requestTime++;
  return {
    nick: "nick",
    age: "18",
  };
});

然后我们要知道resolve中如果是一个Promise对象的话,那么new Promise返回的Promsie对象就是resolve中的Promise对象,这样的话Promise.all中的Promise对象其实都是同一个Promise对象,所以说等待的都是同一Promise对象改变状态返回结果。可以通过打印result的值对比来进行考察。

console.log(result[0] === result[1]) // true
console.log(result[1] === result[2]) // true