Home > OS >  Why does await eval('`${await foo("xxx")}`') throw an error
Why does await eval('`${await foo("xxx")}`') throw an error

Time:08-05

Before I get flamed for using eval()... YES, I know eval === evil and from the devil and should never be used. That is NOT the question here. Believe it or not, I actually have a valid, real life case where it makes perfect sense to use eval, as a matter of fact, there is no other way to achieve the result I require.

Below is a code snippet with 4 cases. The first three works exactly as expected. The first two calls an async function in a template literal. The 3rd and 4th stores the template literal as a string and then eval the string. In the case (no 3) where await is not used, as expected the promise is returned. The problem is however case 4. It throws an error and for the life of me, I can't figure out why, or how to get around it.

I'm hoping someone smarter than me can either come up with a solution so that case 4 can still be evaluated without throwing an error (first prize) or explain why it doesn't work/can't be done.

async function foo(str) {
    return str.toUpperCase();
}

async function bar() {
    const str1 = `${foo("xxx")}`;

    console.log("STR1:", str1);
    // Output: STR1: [object Promise]

    const str2 = `${await foo("xxx")}`;

    console.log("STR2:", str2);
    // Output: STR2: XXX

    const str3 = '`${foo("xxx")}`';

    console.log("STR3:", await eval(str3));
    //Output STR3: [object Promise]

    const str4 = '`${await foo("xxx")}`';

    console.log("STR4:", await eval(str4));
    // undefined:1
    // `${await foo("xxx")}`
    //    ^^^^^^
    // Output: SyntaxError: Missing } in template expression
}

bar();

CodePudding user response:

Another way of making it work, basically by separating the awaiting of the promise from the construction of the string (kind of), using tagged templates is described below:

async function foo(str) {
    return str.toUpperCase();
}

async function asyncFooTmpl(strings, asyncExpr) {
  return `${strings[0]}${await asyncExpr}${strings[1]}`;
}

async function bar() {
    const str1 = `${foo("xxx")}`;

    console.log("STR1:", str1);
    // Output: STR1: [object Promise]

    const str2 = `${await foo("xxx")}`;

    console.log("STR2:", str2);
    // Output: STR2: XXX

    const str3 = '`${foo("xxx")}`';

    console.log("STR3:", await eval(str3));
    //Output STR3: [object Promise]

    const str4 = 'asyncFooTmpl`${foo("xxx")}`';

    console.log("STR4:", await eval(str4));
    //Output STR4: XXX
}

bar();

This transfers the ownership of awaiting the promise from bar to asyncFooTmpl, allowing the string to be produced.

That said, it would require you to write functions for every template you have, or create a function that makes dealing with tagged templates more manageable.

CodePudding user response:

It's not related to eval directly. You just can't use await outside of async function.

This will result in the same error:

const x = `${await Promise.resolve()}`

Wrapping the call in async IIFE solves the issue:

async function foo(str) {
  return str.toUpperCase();
}

async function bar() {
  const x = '`${(async () => await foo("xxx"))()}`'
  eval(x)
}

bar();

  • Related