0

I am developing an Excel add-in using the Office JS API. I have a taskpane which is running code similar to the following:

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; }

I would expect the error in doA to bubble up and interrupt further execution of doB. The output should then be:

Start main
Inside A
Failing A
ERROR: Error A

Instead what I get is:

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

followed by two uncaught exceptions Error A and Error B.

What am I doing wrong? Can I achieve the behavior I expect without wrapping doA and doB separately in try...catch blocks?

2
  • Your fail() and success() functions must return Promise instances, and resolve or reject those when the timer fires. Commented Sep 6, 2023 at 13:32
  • A throw in an asynchronously executed callback (like with setTimeout) will not affect the promise returned by the async function. throw only affects the current callstack. Despite the comment, you're not simulating a failing async call. Commented Sep 6, 2023 at 13:37

2 Answers 2

1

There are a few issues:

  1. fail is not simulating a failing async call. It returns an immediately fulfilling promise. The setTimeout callback that will later throw an error, is irrelevant for that promise, since that callback executes from a fresh call stack. Similarly, success is initiating a setTimeout that has no relevance to the promise it returns, which again gets fulfilled immediately.

  2. run is not linking its resolution to the fate of the promise returned by f and thus run returns a promise that fulfills while f is not monitored for errors. If f rejects, run will not have captured it.

  3. If you configure your script to execute success then you need to await it, otherwise its delay will have no effect.

Here is a fix:

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();

Sign up to request clarification or add additional context in comments.

Comments

1

setTimeout does not know anything about promises. Instead use a promise based sleep for waiting.

Also you forgot to await f() in your run function, or at least return it to keep promise chain intact.

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; }

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.