4

I am starting to use template literals to make a error generator.

I have working code, but I am forced to declare the list of possible errors inside the constructor scope, and I am not pleased with that.

Is there a way to either copy a template literal without evaluating it so I can evaluate it in the right scope? Or pass the scope to the template literal?

Working error.js:

'use strict';

class Error {
    constructor(code) {
        const error = {
            //...
            //API
            1001: 'No token',
            1002: `${arguments[1]}`,
            1003: `${arguments[1]} ! ${arguments[2]}`,
            1004: 'Missing data'
            //...
        };
        let i = 0;
        this.code = code;
        this.error = error[code];
        //...
    }
}

// export default Error;
module.exports = Error;

Called like:

'use strict';
const Error = require('./error.js');

console.log(new Error(1002, 'var'));

What I would like is to be able to declare const error in the module scope, or better yet, in it's own file that I require. But doing so right now lead to argument not being the ones of the constructor, but the one of the module.

DrakaSAN
  • 7,673
  • 7
  • 52
  • 94
  • 3
    You could wrap your template strings in a factory-function. – Leonid Beschastny Oct 25 '16 at 08:53
  • @LeonidBeschastny: I should have thought of that way earlier, I need more coffee. Feel free to write it as a answer. – DrakaSAN Oct 25 '16 at 09:02
  • 1
    Something to keep in mind in all of this is that if `arguments[1]` is a string, you don't need string interpolation at all for "1002: `${arguments[1]}`," – Dawson B Oct 25 '16 at 09:10
  • @DawsonBotsford: `arguments[1]` should be a string in that error, but other errors doesn't have such clear cut type. I am also a fan of consistency as long as it doesn't impact performances meaningfully, so I'll keep the interpolation for now. But it sure is something to know that I didn't saw on MDN. – DrakaSAN Oct 25 '16 at 09:19
  • 1
    Those are **template literals**, not string literals. – Felix Kling Oct 25 '16 at 16:08
  • 1
    Basically a duplicate of [Can ES6 template literals be substituted at runtime (or reused)?](http://stackoverflow.com/q/30003353/218196) and [Defer execution for ES6 Template Literal](http://stackoverflow.com/q/22607806/218196) – Felix Kling Oct 25 '16 at 16:11
  • @FelixKling: I beg to differ, the first question is more a complaint, and confuse string and template litterals. The second, is very close thought, even if it is focused around the custom `String.prototype.format` – DrakaSAN Oct 25 '16 at 16:22
  • 1
    The point is that template literals should be either wrapped with compiler function or be re-implemented. At this point this template syntax isn't more attractive than any other JS template engine over there. – Estus Flask Oct 25 '16 at 18:33

3 Answers3

8

String literals are evaluated immediately. They cannot be used as templates to be formatted later (Unlike for example Python's format strings that look similar).

You could do what Leonid Beschastny suggests and use little functions that does the interpolation for you.

Something like this:

const error = {
    1001: () => 'No token',
    1002: (args) => `${args[1]}`,
    1003: (args) => `${args[1]} ! ${args[2]}`,
    1004: () => 'Missing data'
};
this.error = error[code](arguments);
jaap3
  • 2,696
  • 19
  • 34
  • 2
    Better use spread/rest syntax. Passing `arguments` objects around is pretty inefficient. – Bergi Oct 25 '16 at 16:37
  • @jaap3, there is one instance I know of in which they are NOT evaluated immediately: when they are inside a function. Take a look at my answer below. – abalter Mar 28 '18 at 15:51
1

It's a darn shame that template literals aren't more flexible.

Here are two methods that may be close to what you want.

First:

var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'

To generalify:

var s = (<variable names you want>) => {return `<template with those variables>`}

If you are not running E6, you could also do:

var s = function(<variable names you want>){return `<template with those variables>`}

This seems to be a bit more concise than the previous answers.

https://repl.it/@abalter/reusable-JS-template-literal

Second

class Person
{

  constructor (first, last)
  {
    this.first = first;
    this.last = last;
  }

  sayName ()
  {
        return `Hi my name is ${this.first} ${this.last}`;
  }
}

var bob = new Person("Bob", "Jones")
console.log(bob.sayName()) // Hi my name is Bob Jones

console.log(new Person("Mary", "Smith").sayName()) // Hi my name is Mary Smith

https://repl.it/@abalter/late-evaluation-of-js-template-literal

abalter
  • 9,663
  • 17
  • 90
  • 145
  • Actually, this is a great idea! When you have objects, this is way better than using template tags and associate/render with arrays.. To be even more general, you could simply do ```let template = (o) => `${o.first} ${o.last}` ``` and so on, rendering on any given object – Bernardo Dal Corno Mar 08 '19 at 06:23
0

My preferred solution to pass scope is using this wrapper:

function defer([fisrt, ...rest]) {
  return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, fisrt);
}

That's all. When I want to reuse a template and defer the resolution of the substitutions, I just do:

> t = defer`My template is: ${null} and ${null}`;
> t('simple', 'reusable');          // 'My template is: simple and reusable'
> t('obvious', 'late to the party'; // 'My template is: obvious and late to the party'
> t(null);                          // 'My template is: null and undefined'
>
> defer`Choose: ${'ignore'} / ${undefined}`(true, false); // 'Choose: true / false'

Applying this tag returns back a 'function' (instead of a 'string') that ignores any parameters passed to the literal. Then it can be called with new parameters later. If a parameter has no corresponding replace, it becomes 'undefined'.

You can find more information in those other answers: this and that.

Rodrigo Rodrigues
  • 7,545
  • 1
  • 24
  • 36