likes
comments
collection
share

面试官:来吧!实操一下promise

作者站长头像
站长
· 阅读数 18

背景

参加了szml面试,一共三道笔试题。第一道是画div写样式写逻辑,后续两道是promise的场景应用。后续面试时,面试官着重对笔试题进行了提问,并且对后两个promise的题目进行了一个升级。本文是对第二题的一个总结。

原题复现

以下为第二题的题目

import { isEqual } from 'lodash-es';

/**
 * 第二题
 */

// 核心用户请求
let _requestTime = 0;
const requestUserInfo = () => {
  // 这个方法的实现不能修改
  return Promise.resolve().then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
         resolve();
      }, 1000);
    }).then(() => {
      _requestTime++;
      return {
        nick: 'nick',
        age: '18',
      };
    });
  });
};

const getUserInfo = () => {}

const fn = async () => {
  try {
    // 模拟请求
    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);
    if (
      !isEqual(result, [
        {
          nick: 'nick',
          age: '18',
        },
        {
          nick: 'nick',
          age: '18',
        },
        {
          nick: 'nick',
          age: '18',
        },
      ])
    ) {
      throw new Error('Wrong answer');
    }
    return _requestTime === 1;
  } catch (err) {
    console.warn('测试运行失败');
    console.error(err);
    return false;
  }
};

要求

实现 getUserInfo 方法,getUserInfo 是个通用接口,在各个模块里面都有可能使用。 requestUserInfo 模拟的是请求服务端真正获取用户信息的方法,下面为需求的简要概述:

  • 在一个页面有 A, B, C 3个功能模块,A, B, C 模块渲染执行顺序不可控
  • 每个模块都会调用 getUserInfo 这个方法, 这个方法是可以直接调用 requestUserInfo 获取用户信息
  • 调用三次就会发起三次网络请求
  • 现在需要优化 getUserInfo 这个方法, 保证 getUserInfo 方法3次调用后, 最终只会发出一次网络请求。

实现

/** 是否有请求正在处理 */
let isInRequest = false;
/** 存放结果,后续如果还有请求并且结果有数据则直接返回数据 */
let result = {};
// 缓存后续的请求
let list = [];
const returnAction = (resolve) => {
  list.push(resolve)
  list.forEach((fn) => fn(result))
  list = [];
};
const getUserInfo = () => {
  // 目标是只执行一次,前一次接口调用成功,后续就不需要调用了
  return new Promise((resolve) => {
    if (!isInRequest && !result.nick) {
      isInRequest = true;
      requestUserInfo().then((res) => {
        isInRequest = false;
        if (res.nick) {
          result = {...res};
          // 成功之后
          returnAction(resolve);
        } 
      })
    } else {
      if (isInRequest) {
        // 请求正在进行,将resolve塞入list
        list.push(resolve);
      } else {
        // 请求已完成,直接返回结果
        returnAction(resolve);
      }
    }
  })
}
  1. 设定一个标识isInRequest,是否有请求正在执行,用于后续调用时,让后续调用进行等待。
  2. 第一次请求,此时isInRequest为false且result为空,将isInRequest标识设置为true,表明有接口正在被请求。然后请求接口requestUserInfo,等待处理完成时,将标识设置为false,并且将结果存入result,并将结果通过resolve方法返回。
  3. 在后续请求时,若是isInRequest为true,就将resolve存入list,等待前面的请求完成,在resolve结果
  4. 若是后续请求时,若是isInRequest为false且result有数据,则直接resolve结果
  5. 接第二步,在请求结束时,需要将list中可能存在的resolve给处理
  6. 实现完成,最先调用的接口会执行一次,后续调用只会将第一次的结果拿到返回。

升级

面试官:现在题目是接口一定成功返回,若是接口不一定调用成功,比如第一次不成功,或者前两次都不成功,第三次才成功,这时候该如何处理?

我:面试官:来吧!实操一下promise

改造一下requestUserInfo方法,使得请求可能成功或者失败

const requestUserInfo = () => {
  // 这个方法的实现不能修改
  return Promise.resolve().then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // 模拟请求可能失败的情况
        const random = Math.floor(Math.random() * 10) % 2 === 0;
        if (random) {
          console.log('调用成功')
          resolve();
        } else {
          console.log('调用失败')
          reject();
        }
      }, 1000);
    }).then(() => {
      _requestTime++;
      return {
        nick: 'nick',
        age: '18',
      };
    }, () => {
      return {
        result: false,
      };
    });
  });
};

理清一下思路

  1. 实现方式不变,若是第一次请求失败,则执行第二次请求,所以得设置另一个变量,用于存储在请求执行期间的其它请求
  2. 失败后的请求的resolve也需要存起来,等到后续请求调用成功时,执行返回成功的结果
// 缓存后续的请求
let action = [];

const handleGetInfo = (resolve) => {
  requestUserInfo().then((res) => {
    isInRequest = false;
    if (res.nick) {
      result = {...res};
      // 成功之后
      returnAction(resolve)
    } else {
      // 接口请求失败,将resove塞进list
      list.push(resolve);
      // 若是队列中有待执行的方法,取出一个进行执行
      if (action.length) {
        const fn = action.shift();
        fn();
      }
    }
  })
}
const getUserInfo = () => {
  // 目标是只执行一次,前一次接口调用成功,后续就不需要调用了
  return new Promise((resolve) => {
    if (!isInRequest && !result.nick) {
      isInRequest = true;
      handleGetInfo(resolve);
    } else {
      if (isInRequest) {
        // 说明有请求正在进行
        // 这时候需要等待,存入栈中
        action.push(() => handleGetInfo(resolve))
      } else {
        returnAction(resolve)
      }
    }
  })
}

到这也算是成功的完成了要求。

结语

本篇总结了一下第二题。其实本人对于异步处理Promise一直都不是那么熟练,这几天也是着重看了一下红宝书和其它资料,收获良多。后续第三题是一个请求的并发操作,这几天我也会把它整理出来和大家分享探讨。若是大家有其它的处理方式或者是想沟通交流的内容,欢迎大家留言。

转载自:https://juejin.cn/post/7239925883405877305
评论
请登录