最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Force a consumer to use and entire iterator - Stack Overflow

programmeradmin4浏览0评论

Is there a way to force a consumer to use an entire iterator?

For example:

const [first] = tuple // throws "Need to use both"
const [first, second] = tuple // works

I was hoping something like:

*[Symbol.iterator]() {
    const tuple = this.error
      ? ([null, this.error] as const)
      : ([this.value, null] as const);

    yield tuple[0];

    return {
      done: true,
      value: (() => {
        throw new Error("Must destructure both values from tuple");
      })(),
    };
  }

Would work, but doesn't. Is this even possible? I can't really think of a solid way but would love some help from the big brains.

Is there a way to force a consumer to use an entire iterator?

For example:

const [first] = tuple // throws "Need to use both"
const [first, second] = tuple // works

I was hoping something like:

*[Symbol.iterator]() {
    const tuple = this.error
      ? ([null, this.error] as const)
      : ([this.value, null] as const);

    yield tuple[0];

    return {
      done: true,
      value: (() => {
        throw new Error("Must destructure both values from tuple");
      })(),
    };
  }

Would work, but doesn't. Is this even possible? I can't really think of a solid way but would love some help from the big brains.

Share Improve this question asked Feb 14 at 0:08 Justin WallaceJustin Wallace 1028 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 3

It is possible but I'm not sure why you're doing this. It's time to abuse finally to mess up the control flow if you really insist.

const tuple = {
  *[Symbol.iterator]() {
    let hasError = true;
    try {
      yield 1;
      hasError = false;
      yield 2;
    } finally {
      if(hasError) {
        throw new Error('Must destructure both values from tuple');
      }
    }
  }
}

const [first] = tuple;  // Error: Must destructure both values from tuple
console.log(first);

const tuple = {
  *[Symbol.iterator]() {
    let hasError = true;
    try {
      yield 1;
      hasError = false;
      yield 2;
    } finally {
      if(hasError) {
        throw new Error('Must destructure both values from tuple');
      }
    }
  }
}

const [first, second] = tuple;  // works fine
console.log(first, second);

Why It Works

According to try...catch

Control flow statements (return, throw, break, continue) in the finally block will "mask" any completion value of the try block or catch block. In this example, the try block tries to return 1, but before returning, the control flow is yielded to the finally block first, so the finally block's return value is returned instead.

function doIt() {
  try {
    return 1;
  } finally {
    return 2;
  }
}

doIt(); // returns 2

finally is a bit cursed (but makes sense), it always executes when exiting a try block, regardless of how the exit happens.

Bare that in mind and let's disect this statement:

const [first] = tuple
  1. tuple is referenced, which is the object with an iterator.

  2. const [first] is executed, it's a destructuring statement, that causes *[Symbol.iterator]() to run, and requesting for exactly one value.

let hasError = true;
try {
  yield 1;
  hasError = false;
  yield 2;
} finally {
  if(hasError) {
    throw new Error('Must destructure both values from tuple');
  }
}
  1. After yield 1 is executed, the requested quantity of values (one) has met, so the generator function is halt and ready to exit, but before exiting the try body, the finally has to run.

  2. Because it hasn't reached hasError = false part, the if(hasError) resolves true thus the error is thown before the function truly exits.

However on step 2, if the statement was const [first, second], it's requesting two values instead. So it will not try to exit the try body after yield 1, so hasError = false has reached, thus when the finally block is executed after yield 2, if(hasError) will fail and the error will no longer be thrown.

发布评论

评论列表(0)

  1. 暂无评论