有没有办法短路异步/等待流量?

sbr*_*sbr 31 javascript promise cancellation async-await ecmascript-next

async function update() {
   var urls = await getCdnUrls();
   var metadata = await fetchMetaData(urls);
   var content = await fetchContent(metadata);
   await render(content);
   return;
}
//All the four functions return a promise. (getCdnUrls, fetchMetaData, fetchContent, render)
Run Code Online (Sandbox Code Playgroud)

如果我们想在任何时间从外部中止序列怎么办?

比如,当执行fetchMetaData时,我们意识到不再需要渲染组件,我们想要取消剩余的操作(fetchContent和render).消费者有没有办法从外面中止/取消?

我们可以在每次等待一个条件后检查,但这似乎是一种不太优雅的方式.它仍然会等待当前的操作完成.

Ben*_*aum 24

我刚刚谈了这个 - 这是一个可爱的主题,但遗憾的是你并不会真正喜欢我将要提出的解决方案,因为它们是网关解决方案.

规范对你有什么用

取消"恰到好处"实际上非常困难.人们一直在研究这个问题,并决定不阻止异步功能.

尝试在ECMAScript核心中解决此问题有两个提议:

  • 取消令牌 - 添加旨在解决此问题的取消令牌.
  • 可取消的承诺 - 增加了旨在解决此问题的catch cancel (e) {语法和throw.cancel语法.

这两个提案在过去一周都发生了很大变化,所以我不会指望在明年左右到达.这些建议有点互补,并不存在争议.

您可以做些什么来解决这个问题

取消令牌很容易实现.可悲的是,你真正想要的那种取消(也就是" 取消不是例外的第三州取消)是不可能的,因为你不能控制它们的运行方式,因此你可以做两件事:

  • 使用协同程序 - 蓝鸟通过使用您可以使用的生成器和承诺进行声音取消.
  • 使用流产语义实现令牌 - 这实际上非常简单,所以让我们在这里做

CancellationTokens

好吧,令牌信号取消:

class Token {
   constructor(fn) {
      this.isCancellationRequested = false; 
      this.onCancelled = []; // actions to execute when cancelled
      this.onCancelled.push(() => this.isCancellationRequested = true);
      // expose a promise to the outside
      this.promise = new Promise(resolve => this.onCancelled.push(resolve));
      // let the user add handlers
      fn(f => this.onCancelled.push(f));
   }
   cancel() { this.onCancelled.forEach(x => x); }
}
Run Code Online (Sandbox Code Playgroud)

这可以让你做类似的事情:

async function update(token) {
   if(token.isCancellationRequested) return;
   var urls = await getCdnUrls();
   if(token.isCancellationRequested) return;
   var metadata = await fetchMetaData(urls);
   if(token.isCancellationRequested) return;
   var content = await fetchContent(metadata);
   if(token.isCancellationRequested) return;
   await render(content);
   return;
}

var token = new Token(); // don't ned any special handling here
update(token);
// ...
if(updateNotNeeded) token.cancel(); // will abort asynchronous actions
Run Code Online (Sandbox Code Playgroud)

这是一种非常丑陋的方式,最佳的是你希望异步函数能够意识到这一点,但它们还没有().

最理想的是,你所有的临时功能都会知道并且会throw取消(再次,因为我们不能拥有第三状态),它们看起来像:

async function update(token) {
   var urls = await getCdnUrls(token);
   var metadata = await fetchMetaData(urls, token);
   var content = await fetchContent(metadata, token);
   await render(content, token);
   return;
}
Run Code Online (Sandbox Code Playgroud)

由于我们的每个函数都getCdnUrls可以识别取消,因此它们可以执行实际的逻辑取消 - 可以中止请求和抛出,fetchMetaData可以中止底层请求和抛出等等.

以下是在浏览器中getCdnUrl使用XMLHttpRequestAPI 编写(注意单数)的方法:

function getCdnUrl(url, token) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    var p = new Promise((resolve, reject) => {
      xhr.onload = () => resolve(xhr);
      xhr.onerror = e => reject(new Error(e));
      token.promise.then(x => { 
        try { xhr.abort(); } catch(e) {}; // ignore abort errors
        reject(new Error("cancelled"));
      });
   });
   xhr.send();
   return p;
}
Run Code Online (Sandbox Code Playgroud)

这与没有协同程序的异步函数一样接近.它不是很漂亮但它确实可用.

请注意,您希望避免将取消视为异常.这意味着如果您的throw取消功能需要在全局错误处理程序上过滤这些错误process.on("unhandledRejection", e => ...等.


小智 5

你可以使用 Typescript + Bluebird + cancelable-awaiter获得你想要的东西

既然所有证据都表明取消令牌没有进入 ECMAScript,我认为取消的最佳解决方案是@BenjaminGruenbaum提到的 bluebird 实现,但是,我发现协程和生成器的使用有点笨拙和令人不安.

由于我使用的是 Typescript,它现在支持 es5 和 es3 目标的 async/await 语法,因此我创建了一个简单的模块,用__awaiter支持 bluebird 取消的模块替换默认助手:https : //www.npmjs.com/package /可取消等待者


Mak*_*lau 5

cancellable不幸的是,到目前为止还没有承诺的支持。有一些自定义实现,例如

扩展/包装可取消和可解决的承诺


function promisify(promise) {
  let _resolve, _reject

  let wrap = new Promise(async (resolve, reject) => {
    _resolve = resolve
    _reject = reject
    let result = await promise
    resolve(result)
  })

  wrap.resolve = _resolve
  wrap.reject = _reject
    
  return wrap
}
Run Code Online (Sandbox Code Playgroud)

用法:取消承诺并立即停止进一步执行

async function test() {
  // Create promise that should be resolved in 3 seconds
  let promise = new Promise(resolve => setTimeout(() => resolve('our resolved value'), 3000))
  
  // extend our promise to be cancellable
  let cancellablePromise = promisify(promise)
  
  // Cancel promise in 2 seconds.
  // if you comment this line out, then promise will be resolved.
  setTimeout(() => cancellablePromise.reject('error code'), 2000)

  // wait promise to be resolved
  let result = await cancellablePromise
  
  // this line will never be executed!
  console.log(result)
}
Run Code Online (Sandbox Code Playgroud)

在这种方法中,承诺本身会执行到最后,但是等待承诺结果的调用者代码可以被“取消”。