嵌套异步函数调用中的错误管理 [duplicate]

Error management in nested async function calls [duplicate]

提问人:AndreA 提问时间:9/6/2023 最后编辑:KeithAndreA 更新时间:9/6/2023 访问量:53

问:

我正在使用 Office JS API 开发一个 Excel 加载项。我有一个任务窗格,它正在运行类似于以下内容的代码:

var failA = true
var failB = true

// In reality this is Excel.run, and it injects a context 
async function run(f) { 
  f()
}

// Simulate a failing async call
async function fail(message, delay) {
    setTimeout(()=>{
    throw new Error(message)
   }, delay)
}

// Simulate a successful async call
async function success(message, delay) {
    setTimeout(delay)
}

async function doA() {
    console.log("Inside A");
  if (failA) {
    console.log("Failing A");
    await fail("Error A", 1000)
  } else {
    success("Success A")
  }
  console.log("Done A")
}

async function doB() {
    console.log("Inside B");
  if (failB) {
    console.log("Failing B");
    await fail("Error B", 1000)
  } else {
    success("Success B")
  }
  console.log("Done B")
}

async function main () {
try {
    // This is how Excel.run is called in all the Office samples
    await run(async ()=>{
    console.log("Start main");
    await doA();
    console.log("Between A and B");
    await doB();
    console.log("Finished");
  })}
catch (error) {
    console.log("ERROR: " + error.message)
  }
}

// Need to await main in an async context. In reality main is run from a button
(async () => await main())()
.as-console-wrapper { min-height: 100%!important; top: 0; }

我希望错误冒泡并中断 .然后,输出应为:doAdoB

Start main
Inside A
Failing A
ERROR: Error A

相反,我得到的是:

Start main
Inside A
Failing A
Done A
Between A and B
Inside B
Failing B
Done B
Finished

后跟两个未捕获的异常和 。Error AError B

我做错了什么?我可以在不换行的情况下实现我所期望的行为并单独分装在块中吗?doAdoBtry...catch

JavaScript 异常 async-await office-js

评论

0赞 Pointy 9/6/2023
您的 和 函数必须返回 Promise 实例,并在计时器触发时解析或拒绝这些实例。fail()success()
0赞 trincot 9/6/2023
异步执行的回调(如 )中的 A 不会影响函数返回的 promise。 仅影响当前调用堆栈。尽管有注释,但您不是在模拟失败的异步调用。throwsetTimeoutasyncthrow

答:

1赞 Keith 9/6/2023 #1

setTimeout对承诺一无所知。相反,使用基于承诺的睡眠来等待。

此外,您忘记了在您的函数中,或者至少将其返回以保持承诺链完好无损。await f()run

var failA = true
var failB = true

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// In reality this is Excel.run, and it injects a context 
async function run(f) {
    await f()
}

// Simulate a failing async call
async function fail(message, delay) {
    await sleep(delay);
    throw new Error(message)
}

// Simulate a successful async call
async function success(message, delay) {
    await sleep(delay);
}

async function doA() {
    console.log("Inside A");
    if (failA) {
        console.log("Failing A");
        await fail("Error A", 1000)
    } else {
        success("Success A")
    }
    console.log("Done A")
}

async function doB() {
    console.log("Inside B");
    if (failB) {
        console.log("Failing B");
        await fail("Error B", 1000)
    } else {
        success("Success B")
    }
    console.log("Done B")
}

async function main() {
    try {
        // This is how Excel.run is called in all the Office samples
        await run(async () => {
            console.log("Start main");
            await doA();
            console.log("Between A and B");
            await doB();
            console.log("Finished");
        })
    }
    catch (error) {
        console.log("ERROR: " + error.message)
    }
}

// Need to await main in an async context. In reality main is run from a button
(async () => await main())()
.as-console-wrapper { min-height: 100%!important; top: 0; }

1赞 trincot 9/6/2023 #2

存在以下几个问题:

  1. fail未模拟失败的异步调用。它返回一个立即兑现的承诺。稍后将引发错误的回调与该 promise 无关,因为该回调是从新的调用堆栈执行的。同样,启动一个与它返回的承诺无关的,它再次立即得到实现。setTimeoutsuccesssetTimeout

  2. run没有将其解决方案与 返回的 promise 的命运联系起来,因此返回一个 Promise 在未监控错误的情况下实现。如果拒绝,则不会捕获它。frunffrun

  3. 如果将脚本配置为执行,则需要执行它,否则其延迟将不起作用。successawait

这是一个修复程序:

var failA = true;
var failB = true;

async function run(f) { 
  // Link to the returned promise, so error handling around 
  //    run() will deal with rejections
  return f(); 
}

// Helper function
const expire = ms => new Promise(resolve => setTimeout(resolve, ms));

async function fail(message, delay) {
  await expire(delay);
  // throw must happen in the execution context of function fail
  throw new Error(message); 
}

async function success(message, delay) {
  // Wait for the delay to expire, otherwise it is useless
  await expire(delay); 
}

async function doA() {
  console.log("Inside A");
  if (failA) {
    console.log("Failing A");
    await fail("Error A", 1000);
  } else {
    await success("Success A"); // Must await it
  }
  console.log("Done A");
}

async function doB() {
    console.log("Inside B");
  if (failB) {
    console.log("Failing B");
    await fail("Error B", 1000);
  } else {
    await success("Success B"); // Must await it
  }
  console.log("Done B");
}

async function main () {
  try {
    await run(async ()=>{
        console.log("Start main");
        await doA();
        console.log("Between A and B");
        await doB();
        console.log("Finished");
    })
  } catch (error) {
    console.log("ERROR: " + error.message);
  }
}

main();