一道经典的Promise面试题

最近在总结异步的一些实现方式,也是翻出了一道比较经典的 promise 面试题与大家分享。
当然具体实现代码比较长,所以面试只是问了思路。今天找时间把它实现出来。

进入正题
现假设后端有一个服务,用于批量获取书籍信息,接受一个数组作为请求参数,数组储存了需要获取书籍信息的书籍 id,
这个服务 fetchBooksInfo 大概是这个样子:

   const fetchBooksInfo = bookIdList => {
     const data = [
       {
         id: 123
         // ...
       },
       {
         id: 456
         // ...
       },
       {
         id: 789
         // ...
       }
       // ...
     ];
     // 模拟查找数据逻辑 并返回promise对象
     let result = data.filter(item => bookIdList.indexOf(item.id) > -1);
     return Promise.resolve(result);
   };
需求

现有如下要求:

  1. fetchBooksInfo 已经给出,但是这个接口单次只支持最多 100 个 id 的查询。
  2. 现需要实现 getBooksInfo 方法,该方法的要求为:
    2-1. 支持调用单个书目信息,如:
    getBooksInfo(123).then(data => { console.log(data.id) }) // 123
    2-2. 短时间(100 毫秒)内多次连续调用,只请求一次 fetchBooksInfo 服务,且获得各个书目信息,如:
    getBooksInfo(123).then(data => { console.log(data.id) }) // 123
    getBooksInfo(456).then(data => { console.log(data.id) }) // 456
  3. 要考虑服务端出错的情况,比如批量接口请求[123, 446] 书目信息,但是服务端只返回了书目123的信息。此时应该进行合理的错误处理。
  4. 对 ID 重复进行处理
思路

认真读完要求后,来分析下题目,梳理思路:

  1. 首先要对 100 毫秒内的连续请求进行合并,只触发一次请求。考虑可以使用数组存储多条请求的id,并使用定时器处理 100 毫秒的延迟。
  2. 请求的数据长度达到 100 时,清除定时器,直接发送请求。
  3. 还要通过返回的数据判断是否包含全部请求参数中的数据,如果缺失需要抛出错误提示。
  4. 使用 promise 毋庸置疑,我觉得这道题最重要的考点就在于如何在合适的时机进行 reject 或 resolve 对应的 promise 实例。我的做法是对每一个 getBooksInfo 对应的 promise 实例的 reject 和 resolve 方法进行存储。以便于可以在需要的时候进行触发。
实现

完整的代码实现:

      // 题目给出的服务代码
      const fetchBooksInfo = bookIdList => {
        const data = [
          {
            id: 123
            // ...
          },
          {
            id: 456
            // ...
          },
          {
            id: 789
            // ...
          }
          // ...
        ];
        // 模拟查找数据逻辑 并返回promise对象
        let result = data.filter(item => bookIdList.indexOf(item.id) > -1);
        return Promise.resolve(result);
      };

      // 以下为我的代码实现↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        
      // 储存将要请求的 id 数组
      let bookIdListToFetch = [];

      // 用于储存每个 id 请求 promise 实例的 resolve 和 reject
      // key 为 bookId,value 为 resolve 和 reject 方法
      // 如: { 123: [{resolve, reject}]}
      let promiseMap = {};

      // 数组去重的方法
      const getUniqueArray = array => Array.from(new Set(array));

      // 定时器 id
      let timer;

      // getBooksInfo 方法
      const getBooksInfo = bookId =>
        new Promise((resolve, reject) => {
          promiseMap[bookId] = promiseMap[bookId] || [];
          // 保存每个 id 请求 promise 实例的 resolve 和 reject
          promiseMap[bookId] = {
            resolve,
            reject
          };

          // 清空数据
          const clearTask = () => {
            bookIdListToFetch = [];
            promiseMap = {};
          };

          // 如果数组为空
          if (bookIdListToFetch.length === 0) {
            bookIdListToFetch.push(bookId);

            // 定时100ms 到时自动执行
            timer = setTimeout(() => {
              // 调用服务获取数据 && 清空存储
              handleFetch(bookIdListToFetch, promiseMap);
              clearTask();
            }, 100);
          } else {
            // 如果数组已有数据
            // 添加进数组
            bookIdListToFetch.push(bookId);

            // 去重
            bookIdListToFetch = getUniqueArray(bookIdListToFetch);

            // 数量大于100
            if (bookIdListToFetch.length >= 100) {
              // 清除计时器 && 调用服务获取数据 && 清空存储
              clearTimeout(timer);
              handleFetch(bookIdListToFetch, promiseMap);
              clearTask();
            }
          }
        });

      // 调用服务获取数据
      const handleFetch = (list, map) => {
        // 获取图书信息
        fetchBooksInfo(list).then(resultArray => {
          console.log("调用服务");

          // 保存获取的图书 bookId
          const resultIdArray = resultArray.map(item => item.id);

          // 处理存在的 bookId
          resultArray.forEach(data => map[data.id].resolve(data));

          // 处理失败没拿到的 bookId
          let rejectIdArray = [];
          list.forEach(id => {
            // 返回的数组中,不含有某项 bookId,表示请求失败
            if (!resultIdArray.includes(id)) {
              rejectIdArray.push(id);
            }
          });

          // 对请求失败的数组进行 reject
          rejectIdArray.forEach(id => map[id].reject(id));
        });
      };

至此所有代码已经完成,接下来验证 getBooksInfo 方法是否满足需求。

验证1
      getBooksInfo(123).then(data => {
        console.log(data.id); // 123
      });
      getBooksInfo(456).then(data => {
        console.log(data.id); // 456
      });

执行结果:


在这里插入图片描述

两次调用 getBooksInfo 方法,均返回数据,并且是只调用了一次 fetchBooksInfo 服务,满足需求。

验证2

模拟一次失败的获取,因为“服务端”没有446这条数据,返回的数据必然缺少一条.

      getBooksInfo(446)
        .then(data => {
          console.log(data.id);
        })
        .catch(data => {
          console.log(`获取数据${data}出错了`); // 获取数据 446 出错了
        });

执行结果:


在这里插入图片描述

程序正常抛出错误提示。

完结
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容