多次调用promise只返回最后一个的结果

题目描述

多次调用promise模拟发送请求,只返回最后一个结果

题目来源及自己的思路

面试的一个题,没写出来

相关代码

粘贴代码文本(请勿用截图)

// 发送请求,只获取最后一次的结果,之前promise没完成的话就取消
function wrapper(fn) {
    // your code
}

let count = 0
function sendRequest() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(++count)
        })
    })
}

let newWrap = wrapper(sendRequest)
newWrap().then(console.log)
newWrap().then(console.log)
newWrap().then(console.log)   // 3
阅读 4.9k
3 个回答

这题前面取消的话最后执行的就应该是返回1而不是3,不过仅仅是类似于节流或者防抖的区别,节流就应该是返回3,防抖就应该是返回1
你看下这样的话是否符合题意

        function wrapper(fn) {
            // your code
            let isEnd = false
            let timer;
            return function() {
                clearTimeout(timer)
                timer = setTimeout(()=> isEnd = true)
                return new Promise(rs=> {
                    //节流
                    setTimeout(()=> isEnd ? rs(fn()) : fn())
                    //防抖
                    setTimeout(()=> isEnd && rs(fn()))
                })
            }
        }

题目有问题吧,是无论调用几次都只返回最后一次的结果?前面的是否要取消?如果前面的失败了,后面的还要么?

我随便挑了种情况:

  1. 前面不取消
  2. 某个失败就失败
// return-last-one.js
let lastOne;
let mainPromise;
let mainResolve;
let mainReject;
export function returnLastOne(params) {
  // 生成一个主 promise,用来封装真正的 promise
  if (!mainPromise) {
    mainPromise = new Promise((resolve, reject) => {
      mainResolve = resolve;
      mainReject = reject;
    });
  }

  // 真正的 promise
  const thisOne = lastOne = fetch(params)
    .then(response => response.json())
    .then((json) => {
      // 利用局部变量和上层变量判断是否是最后一次
      if (thisOne === lastOne) {
        // 是的话,清理封装,返回结果
        mainPromise = null;
        mainResolve(json);
      }      
    })
    .catch(e => mainReject(e));
  return mainPromise;
}

(没调试。写完发现有点偏题……不过思路就是这样,题主把改写当练习吧)

看样子像显了防抖。

方法一,每次调用的时候,设置一个标志。每次调用都改变这个标志,只有异步完成跟标志比较吻合的才返回。
方法二,思路其实差不多,只是不等之前的调用返回,而是直接取消(或中断)之前的调用。

来看看这篇:可以中断的异步操作

function wrapper(fn) {
    let mark = 0;
    return async (...args) => {
        // 每次调用改变 mark(这里是 +1)
        // 同时记录一这个 mark 到 waitMark 里,用于结束时比较
        const waitMark = ++mark;
        // 等待异步调用结束
        const result = await fn(...args);
        // 结束后比较,看 mark 标志是否被其他调用改变
        // 没改变说明直到结束都没有另一个调用(虽然不一定是最后一个)
        if (mark === waitMark) {
            return result;
        } else {
            // 如果有改变,因为是返回的 Promise,一定需要 reject,否则 Promise 会挂起
            // 这里用的 aysnc 自动封装的 Promise,throw 就是 reject
            // 如果不加 else 分支,下面的前两个调用会输出 undefined
            // 当然可以手写返回 Promise 对象来实现一直挂起,但不推荐
            throw new Error("cancel");
        }
    };
}

下面的调用要改一下,加上对 catch 的处理(或忽略)

let newWrap = wrapper(sendRequest);
// 因为上面所述的原因,需要 catch
newWrap().then(console.log).catch(() => { });
newWrap().then(console.log).catch(() => { });
newWrap().then(console.log).catch(() => { });   // 3
推荐问题
宣传栏