【问题标题】:Abort asynchronous function from background proccess从后台进程中止异步函数
【发布时间】:2021-08-29 03:43:17
【问题描述】:

我有一个异步函数,我想从中止正在后台运行的进程。 中止不需要取消异步函数内部当前未决的任何承诺。 它只需要使函数返回并防止任何进一步的行被执行。 我怎么能这样做? 当前代码不起作用,因为错误是在 setInterval 而不是 try 块中引发的。


setInterval(() => { //background proccess
  abort();
}, 5000); 

async function func() {
  try {
    abort = function thr() {
      throw new Error("dsaf");
    };
    await new Promise((r) => setTimeout(r, 10000));
    await new Promise((r) => setTimeout(r, 10000));
    await new Promise((r) => setTimeout(r, 10000));
    await new Promise((r) => setTimeout(r, 10000));
    await new Promise((r) => setTimeout(r, 10000));
    await new Promise((r) => setTimeout(r, 10000));
    await new Promise((r) => setTimeout(r, 10000));
  } catch {
    console.log("caught");
  }  
}   

func()

更新:是否有其他语言更好地支持这种类型的模式,或者所有语言都这样?

【问题讨论】:

  • 这里至少有一个很大的误解。 setInterval 不是后台进程。此外,所有等待的承诺都不会中止。
  • 我不知道术语是否正确,但它是一个始终运行而不返回的函数。
  • javascript 中不存在“一直运行而不返回”的功能。

标签: javascript node.js promise cancellation


【解决方案1】:

你可能正在寻找

let throwIfAborted = () => {};

setInterval(() => { //background proccess
  throwIfAborted = () => { throw new Error("Abort"); };
}, 5000);

async function func() {
  try {
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
    await new Promise((r) => setTimeout(r, 10000));
    throwIfAborted();
  } catch {
    console.log("caught");
  }  
}   

func()

【讨论】:

  • 有没有更干净的方法来做到这一点?理想情况下,即使不使用 try-catch 来短路?
  • @yyyyrrrrreee 您也可以使用布尔标志并写入if (aborted) return,但实际上没有更好的方法来实现这一点。 (您说您不想 clearTimeout 当前活动的超时 - 但是这会使承诺无法解决,这也不是一个好主意)。
  • 哦,是的,这是可能的,但有没有办法停止重复 throwIfAborted()if (aborted) return
  • @yyyyrrrrreee 不。如果您希望在每条语句之后中止函数,则必须在每条语句之后检查该函数是否应该中止...
  • @yyyyrrrrreee 你可以通过使用生成器函数和yield 而不是await 来解决这个问题 - 我曾经有一次written a promise library 支持这一点。其他语言以不同的方式执行此操作,例如 Python。具有可编写脚本的 async/await 语法和 the trio library has superb cancellation.
【解决方案2】:

使用CPromise 包,可以按如下方式完成(Live sandbox):

import { CPromise } from "c-promise2";

const func = CPromise.promisify(function* () {
  try {
    yield new Promise((r) => setTimeout(r, 10000));
    yield new Promise((r) => setTimeout(r, 10000));
    yield new Promise((r) => setTimeout(r, 10000));
    yield new Promise((r) => setTimeout(r, 10000));
    yield new Promise((r) => setTimeout(r, 10000));
    yield new Promise((r) => setTimeout(r, 10000));
    yield new Promise((r) => setTimeout(r, 10000));
  } catch (err) {
    console.log(`caught: ${err}`);
  }
});

const promise = func();

setInterval(() => {
  promise.cancel();
}, 5000);

【讨论】:

    【解决方案3】:

    为了让生活更轻松,您可以创建一个辅助函数。

    下面我创建了一个简单的可中止函数,您只需将异步代码放入run,然后在准备好时调用abort

    例如。

    const sleep = ms => new Promise(r => setTimeout(r, ms));
    
    function abortable() {
      let aborted = false;
      return {
        abort: () => aborted = true,
        run: async cb => {
          if (aborted) throw new Error('Aborted');
          return await cb();
        }
      }
    }
    
    
    async function hello(caption, ms) {
      await sleep(ms);
      return `Ran ${caption}`;
    }
    
    
    const {abort, run} = abortable();
    setTimeout(() => abort(), 2000);
    
    async function test() {
      try {
        console.log(await run(() => hello('One', 1500)));
        console.log(await run(() => hello('Two', 1500)));
        //this next one should not run, as it's over 2000ms
        console.log(await run(() => hello('Three', 1500)));
      } catch (e) {
        console.log(e.message);
      }
    }
    
    test();

    使用生成器,就像@Dmitriy 对 CPromise 的回答看起来不错,唯一的小问题是,如果下一个需要来自前一个的返回值。一个简单的解决方案是使用某种形式的状态并将其传递给每个函数。

    以下是使用生成器的示例,但没有任何 3rd 方库。

    const sleep = ms => new Promise(r => setTimeout(r, ms));
    
    function abortable() {
      let aborted = false;
      return {
        abort: () => aborted = true,
        run: async p => {
          for (const r of p()) {
            if (aborted) throw new Error('aborted');
            console.log(await r);
          }
        }
      }
    }
    
    
    async function hello(state, value, ms) {
      await sleep(ms);
      state.total += value;
      return `Total ${state.total}`;
    }
    
    const {abort, run} = abortable();
    setTimeout(() => abort(), 2000);
    
    run(function *() {
      const state = {total: 0};
      yield hello(state, 5, 1500);
      yield hello(state, 10, 1500);
      yield hello(state, 15, 1500);
    }).catch(e => console.log(e.message));

    【讨论】:

    • @yyyyrrrrreee 不管你是从哪里调用 abort,我已经更新了我的 sn-p 以将 abortable 放在异步函数之外。
    • 抱歉,这确实有效,并且与 Bergi 的代码做同样的事情,但没有重复。
    • @yyyyrrrrreee 事实上,它确实有相同的重复。只是写的不一样。您现在无需在每个等待的函数之后调用throwIfAborted(),而是多次调用run(() => something async)。这完全一样,即检查是否设置了abort 标志,如果是,则会引发错误......因为这是您可以实现的唯一方法,您想要什么。您必须在每条语句之后检查当前函数是否应该中止...
    • 是的,没错,但写法不同。虽然我确实认为这更具可读性。
    猜你喜欢
    • 2011-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-01
    • 2019-10-25
    • 1970-01-01
    相关资源
    最近更新 更多