JS Promise - 立即从返回 Promise 的函数中检索一些数据

JS Promise - instantly retrieve some data from a function that returns a Promise

本文关键字:Promise 检索 数据 函数 返回 JS      更新时间:2023-09-26

任何人都可以推荐一种模式来立即从返回 Promise 的函数中检索数据吗?

我的(简化(示例是一个 AJAX 预加载器:

loadPage("index.html").then(displayPage);

如果这是下载一个大页面,我希望能够检查发生了什么,并可能在稍后阶段使用 XHR abort(( 取消该过程。

我的 loadPage 函数曾经(在 Promise 之前(返回一个 id,让我稍后执行此操作:

var loadPageId = loadPage("index.html",displayPage);
...
doSomething(loadPageId);
cancelLoadPage(loadPageId);

在我的新的基于Promise的版本中,我想cancelLoadPage((会拒绝((原来的loadPage((承诺。

我已经考虑了一些我不喜欢的选项。有没有一种普遍接受的方法来实现这一点?

好的,让我们先解决你的赏金问题。

[希望我能把积分授予那些说"不要使用承诺"的人......

对不起,但这里的答案是:"不要使用承诺"。ES6 承诺有三种可能的状态(对于作为用户的您(:挂起、已解决和已拒绝(名称可能略有偏差(。

你没有办法看到承诺的"内部",看看已经做了什么,什么没有做——至少在原生 ES6 承诺中没有。在承诺通知上做了一些有限的工作(在其他框架中(,但这些工作没有进入 ES6 规范,所以即使你找到了它的实现,使用它也是不明智的。

承诺旨在表示将来某个时间点的异步操作;独立地,它不适合此目的。你想要的可能更类似于事件发布者 - 即使它是异步的,而不是同步的。

没有

安全的方法可以从异步调用中同步获取一些价值,尤其是在 JavaScript 中。造成这种情况的主要原因之一是,如果一个好的 API 可以是异步的,它将永远是异步的。

请考虑以下示例:

const promiseValue = Promise.resolve(5)
promiseValue.then((value) => console.log(value))
console.log('test')

现在,让我们假设这个承诺(因为我们提前知道值(是同步解决的。你期待看到什么?您希望看到:

> 5
> test

但是,实际发生的是这样的:

> test
> 5

这是因为即使Promise.resolve()是解析已解析的 Promise 的同步调用,then()也将始终是异步的;这是规范的保证之一,也是一个很好的保证,因为它使代码更容易推理 - 想象一下,如果你尝试混合同步和异步承诺会发生什么。

顺便说一下,这适用于所有异步调用:JavaScript 中任何可能异步的操作都将是异步的。因此,您无法在 JavaScript 提供的任何 API 中进行任何类型的同步内省。

这并不是说你不能围绕请求对象制作某种包装器,如下所示:

function makeRequest(url) {
  const requestObject = new XMLHttpRequest()
  const result = {
  }
  result.done = new Promise((resolve, reject) => {
    requestObject.onreadystatechange = function() {
      ..
    }
  })
  requestObject.open(url)
  requestObject.send()
  return requestObject
}

但是这变得非常混乱,非常快,你仍然需要使用某种异步回调才能正常工作。当您尝试使用Fetch时,这一切都会失败。另请注意,承诺取消目前不是规范的一部分。有关该特定位的更多信息,请参阅此处。

TL:DR:在 JavaScript 中的任何异步操作上都不可能进行同步内省,如果你甚至尝试过 Promise,它也不是要走的路。例如,您无法同步显示有关正在进行的请求的信息。在其他语言中,尝试执行此操作需要阻止或争用条件。

嗯。如果使用角度,则可以在需要取消和正在进行的 HTTP 请求时使用$http服务使用的timeout参数。

打字稿中的示例:

interface ReturnObject {
  cancelPromise: ng.IPromise;
  httpPromise: ng.IHttpPromise;
}
@Service("moduleName", "aService")
class AService() {
  constructor(private $http: ng.IHttpService
              private $q: ng.IQService) { ; }
  doSomethingAsynch(): ReturnObject {
    var cancelPromise = this.$q.defer();
    var httpPromise = this.$http.get("/blah", { timeout: cancelPromise.promise });
    return { cancelPromise: cancelPromise, httpPromise: httpPromise };
  }
}
@Controller("moduleName", "aController")
class AController {
  constructor(aService: AService) {
    var o = aService.doSomethingAsynch();
    var timeout = setTimeout(() => {
      o.cancelPromise.resolve();
    }, 30 * 1000);
    o.httpPromise.then((response) => {
      clearTimeout(timeout);
      // do code
    }, (errorResponse) => {
      // do code
    });
  }
}

由于这种方法已经返回了一个具有两个 promise 的对象,因此在该对象中包含任何同步操作返回数据的延伸并不远。

如果您可以描述要从此类方法同步返回的数据类型,这将有助于识别模式。为什么它不能是在异步操作之前或期间调用的另一个方法?

你可以这样做,但 AFAIK 这将需要黑客的解决方法。请注意,导出 resolvereject 方法通常被认为是 promise 反模式(即,签名您不应该使用承诺(。请参阅底部,了解使用setTimeout的东西,这些内容可能会在没有解决方法的情况下为您提供所需的内容。

let xhrRequest = (path, data, method, success, fail) => {
  const xhr = new XMLHttpRequest();
  // could alternately be structured as polymorphic fns, YMMV
  switch (method) {
    case 'GET':
      xhr.open('GET', path);
      xhr.onload = () => {
          if (xhr.status < 400 && xhr.status >= 200) {
            success(xhr.responseText);
            return null;
          } else {
            fail(new Error(`Server responded with a status of ${xhr.status}`));
            return null;
          }
        };
        xhr.onerror = () => {
          fail(networkError);
          return null;
        }
        xhr.send();
        return null;
      }
      return xhr;
    case 'POST':
      // etc.
      return xhr;
    // and so on...
};
// can work with any function that can take success and fail callbacks
class CancellablePromise {
  constructor (fn, ...params) {
    this.promise = new Promise((res, rej) => {
      this.resolve = res;
      this.reject = rej;
      fn(...params, this.resolve, this.reject);
      return null;
    });
  }
};
let p = new CancellablePromise(xhrRequest, 'index.html', null, 'GET');
p.promise.then(loadPage).catch(handleError);
// times out after 2 seconds
setTimeout(() => { p.reject(new Error('timeout')) }, 2000);
// for an alternative version that simply tells the user when things 
// are taking longer than expected, NOTE this can be done with vanilla 
// promises:
let timeoutHandle = setTimeout(() => {
  // don't use alert for real, but you get the idea
  alert('Sorry its taking so long to load the page.');
}, 2000);
p.promise.then(() => clearTimeout(timeoutHandle));

承诺是美丽的。我认为你没有任何理由不能用承诺来处理这个问题。我能想到三种方法。

  1. 处理此问题的最简单方法是在执行器中。如果你想取消承诺(例如因为超时(,你只需在执行器中定义一个timeout标志,并使用setTimeout(_ => timeout = true, 5000)指令打开它,并且仅在超时为假时才解析或拒绝。即(!timeout && resolve(res)!timeout && reject(err)(这样,在超时的情况下,您的承诺将无限期地保持未解决,并且您在then阶段的onfulfillmentonreject函数永远不会被调用。
  2. 第二个与第一个非常相似,但不是保留一个标志,您只需在超时时调用reject正确的错误描述即可。并在thencatch阶段处理其余的工作。
  3. 但是,如果您想将 asych 操作的 id 携带到同步世界,那么您也可以执行以下操作;

在这种情况下,您必须自己承诺异步函数。让我们举个例子。我们有一个异步函数来返回数字的双倍。这是函数

function doubleAsync(data,cb){
  setTimeout(_ => cb(false, data*2),1000);
}

我们想使用承诺。所以通常我们需要一个 promissifier 函数,它将采用我们的异步函数并返回另一个函数,该函数在运行时会获取我们的数据并返回一个承诺。右。。?所以这里是承诺函数;

function promisify(fun){
  return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}

让我们看看它们如何协同工作;

function promisify(fun){
  return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}
function doubleAsync(data,cb){
  setTimeout(_ => cb(false, data*2),1000);
}
var doubleWithPromise = promisify(doubleAsync);
doubleWithPromise(100).then(v => console.log("The asynchronously obtained result is: " + v));

所以现在你看到我们的 doubleWithPromise(data) 函数返回一个承诺,我们将一个then阶段链接到它并访问返回的值。

但你需要的不仅仅是一个承诺,还有你的异步功能的id。这很简单。你的承诺函数应该返回一个具有两个属性的对象;一个承诺和一个ID。 让我们看看...

这次我们的异步函数将在 0-5 秒内随机返回结果。我们将与result.promise同步获取它result.id,如果它未能在 2.5 秒内解析,则使用此 id 取消承诺。控制台日志Resolves in 2501 msecs或以上的任何数字都不会发生任何事情,并且承诺实际上被取消。

function promisify(fun){
  return function(data){
           var result = {id:null, promise:null};       // template return object
           result.promise = new Promise((resolve,reject) => result.id = fun(data, (err,res) => err ? reject(err) : resolve(res)));
           return result;
         };
}
function doubleAsync(data,cb){
  var dur = ~~(Math.random()*5000);                    // return the double of the data within 0-5 seconds.
  console.log("Resolve in " + dur + " msecs");
  return setTimeout(_ => cb(false, data*2),dur);
}
var doubleWithPromise = promisify(doubleAsync),
       promiseDataSet = doubleWithPromise(100);
setTimeout(_ => clearTimeout(promiseDataSet.id),2500); // give 2.5 seconds to the promise to resolve or cancel it.
promiseDataSet.promise
              .then(v => console.log("The asynchronously obtained result is: " + v));

您可以使用

fetch()Response.body.getReader() ,其中调用 .read() 时返回具有 cancel 方法的ReadableStream,该方法在取消流读取时返回Promise

// 58977 bytes of text, 59175 total bytes
var url = "https://gist.githubusercontent.com/anonymous/"
          + "2250b78a2ddc80a4de817bbf414b1704/raw/"
          + "4dc10dacc26045f5c48f6d74440213584202f2d2/lorem.txt";
var n = 10000;
var clicked = false;
var button = document.querySelector("button");
button.addEventListener("click", () => {clicked = true});
fetch(url)
.then(response => response.body.getReader())
.then(reader => {
  var len = 0;
  reader.read().then(function processData(result) {
    if (result.done) {
      // do stuff when `reader` is `closed`
      return reader.closed.then(function() {
        return "stream complete"
      });
    };
    if (!clicked) {
      len += result.value.byteLength;
    }
    // cancel stream if `button` clicked or 
    // to bytes processed is greater than 10000
    if (clicked || len > n) {
      return reader.cancel().then(function() {
        return "read aborted at " + len + " bytes"
      })
    }
    console.log("len:", len, "result value:", result.value);
    return reader.read().then(processData)
  })
  .then(function(msg) {
    alert(msg)
  })
  .catch(function(err) {
    console.log("err", err)
  })
});
<button>click to abort stream</button>

我目前使用的方法如下:

var optionalReturnsObject = {};
functionThatReturnsPromise(dataToSend, optionalReturnsObject ).then(doStuffOnAsyncComplete);
console.log("Some instant data has been returned here:", optionalReturnsObject ); 

对我来说,这样做的好处是我的团队的另一个成员可以以一种简单的方式使用它:

functionThatReturnsPromise(data).then(...);

并且无需担心返回对象。高级用户可以从定义中看到正在发生的事情。