Promise - 是否可以强制取消一个 Promise?

176
我使用ES6 Promise来管理我的所有网络数据获取,但有些情况下我需要强制取消它们。
基本上场景是这样的:我在UI上使用了一个自动完成搜索功能,该请求被委托给后端进行搜索。虽然这个网络请求(#1)可能需要一点时间,但用户继续输入,最终触发了另一个后端调用(#2)。
在这里,#2自然优先于#1,因此我想取消Promise封装的#1请求。我已经在数据层中有了所有Promise的缓存,因此理论上,当我尝试提交#2的Promise时,可以检索到#1的Promise。
但是,我如何取消从缓存中检索出来的Promise #1呢?
有人能提供一种方法吗?

2
有没有使用防抖函数的等效选项,以避免触发过于频繁和变得过时的请求?比如说300毫秒的延迟就可以解决问题。例如,Lodash有一个实现-https://lodash.com/docs#debounce - shershen
这就是Bacon和Rx这样的东西派上用场的时候。 - elclanrs
@shershen 是的 - 我们有这个功能,但这不是关于用户界面问题的。服务器查询可能需要一些时间,因此我希望能够取消 Promises... - Moonwalker
.catch(_=>_)附加到代码中以避免未来可能出现的未捕获Promise错误,这样做有什么问题吗?它会在虚空中悬浮吗? - Redu
显示剩余3条评论
13个回答

0

我发现这里发布的解决方案有点难以阅读,因此我创建了一个辅助函数,我认为它更易于使用。

这个辅助函数提供了访问当前调用是否已经过时的信息。有了这些信息,函数本身必须相应地处理事情(通常只需返回即可)。

// Typescript
export function obsoletableFn<Res, Args extends unknown[]>(
  fn: (isObsolete: () => boolean, ...args: Args) => Promise<Res>,
): (...args: Args) => Promise<Res> {
  let lastCaller = null;

  return (...args: Args) => {
    const me = Symbol();
    lastCaller = me;

    const isObsolete = () => lastCaller !== me;

    return fn(isObsolete, ...args);
  };
}

// helper function
function obsoletableFn(fn) {
  let lastCaller = null;
  return (...args) => {
    const me = Symbol();
    lastCaller = me;
    const isObsolete = () => lastCaller !== me;
    return fn(isObsolete, ...args);
  };
}

const simulateRequest = () => new Promise(resolve => setTimeout(resolve, Math.random() * 2000 + 1000));

// usage
const myFireAndForgetFn = obsoletableFn(async(isObsolete, x) => {
  console.log(x, 'starting');
  await simulateRequest();
  if (isObsolete()) {
    console.log(x, 'is obsolete');
    // return, as there is already a more recent call running
    return;
  }
  console.log(x, 'is not obsolete');
  document.querySelector('div').innerHTML = `Response ${x}`;
});

myFireAndForgetFn('A');
myFireAndForgetFn('B');
<div>Waiting for response...</div>


0

使用外部包提供的Promise子类,可以按照以下方式完成:在线演示

import CPromise from "c-promise2";

function fetchWithTimeout(url, {timeout, ...fetchOptions}= {}) {
    return new CPromise((resolve, reject, {signal}) => {
        fetch(url, {...fetchOptions, signal}).then(resolve, reject)
    }, timeout)
}

const chain= fetchWithTimeout('http://localhost/')
    .then(response => response.json())
    .then(console.log, console.warn);

//chain.cancel(); call this to abort the promise and releated request

-1
因为@jib拒绝了我的修改,所以我在这里发布了我的答案。这只是对@jib's anwser的修改,加上了一些注释并使用更易懂的变量名。
下面我只展示两种不同方法的示例:一个是resolve(),另一个是reject()。

let cancelCallback = () => {};

input.oninput = function(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancelCallback(); //cancel previous promise by calling cancelCallback()

  let setCancelCallbackPromise = () => {
    return new Promise((resolve, reject) => {
      // set cancelCallback when running this promise
      cancelCallback = () => {
        // pass cancel messages by resolve()
        return resolve('Canceled');
      };
    })
  }

  Promise.race([setCancelCallbackPromise(), getSearchResults(term)]).then(results => {
    // check if the calling of resolve() is from cancelCallback() or getSearchResults()
    if (results == 'Canceled') {
      console.log("error(by resolve): ", results);
    } else {
      console.log(`results for "${term}"`, results);
    }
  });
}


input2.oninput = function(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancelCallback(); //cancel previous promise by calling cancelCallback()

  let setCancelCallbackPromise = () => {
    return new Promise((resolve, reject) => {
      // set cancelCallback when running this promise
      cancelCallback = () => {
        // pass cancel messages by reject()
        return reject('Canceled');
      };
    })
  }

  Promise.race([setCancelCallbackPromise(), getSearchResults(term)]).then(results => {
    // check if the calling of resolve() is from cancelCallback() or getSearchResults()
    if (results !== 'Canceled') {
      console.log(`results for "${term}"`, results);
    }
  }).catch(error => {
    console.log("error(by reject): ", error);
  })
}

function getSearchResults(term) {
  return new Promise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    setTimeout(() => resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
  });
}
Search(use resolve): <input id="input">
<br> Search2(use reject and catch error): <input id="input2">


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接