web-dev-qa-db-ja.com

`.map`コールバック内からの戻りを生成しないのはなぜですか?

Learn Generators-4"CATCH ERROR! ソリューションはfor loopを使用しますが、コールバック内のyieldを参照する MDN-Iteration Protocols で何も見つかりませんでした。

答えはdon't do thatだけだと思いますが、誰かが説明する時間や傾向がある場合は、事前に感謝します!

コード:

function *upper (items) {
  items.map(function (item) {
    try {
      yield item.toUpperCase()
    } catch (e) {
      yield 'null'
    }
  }
}

var badItems = ['a', 'B', 1, 'c']

for (var item of upper(badItems)) {
  console.log(item)
}
// want to log: A, B, null, C

エラー:

⇒  learn-generators run catch-error-map.js
/Users/gyaresu/programming/projects/nodeschool/learn-generators/catch-error-map.js:4
      yield item.toUpperCase() // error below
            ^^^^
SyntaxError: Unexpected identifier
    at exports.runInThisContext (vm.js:73:16)
    at Module._compile (module.js:443:25)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

私の編集者もこれがひどい考えであることを知っています...

yield within callback

16
Gareth

免責事項:私は Learn generators Workshopperの作成者です。

@slebetmanの回答は正解で、さらに追加することもできます。

はい、 MDN-反復プロトコル はコールバック内でyieldを直接参照しません。ただし、yieldgenerators内でのみ使用できるため、yieldアイテムのどこから重要性を示すかがわかります。詳細は MDN-Iterables docsを参照してください。

@ marocchinosuggest マッピング後に変更された配列を繰り返し処理します:

function *upper (items) {
  yield* items.map(function (item) {
    try {
      return item.toUpperCase();
    } catch (e) {
      return null;
    }
  });
}

Arrayには反復メカニズムがあるため、それを行うことができます。 Array.prototype [@@ iterator]() を参照してください。

var bad_items = ['a', 'B', 1, 'c'];

for (let item of bad_items) {
  console.log(item); // a B 1 c
}

Array.prototype.map にはデフォルトの反復動作がないため、反復できませんでした。

しかし、ジェネレータは単なるイテレータではありません。すべてのジェネレータはイテレータですが、その逆はありません。ジェネレーターでは、yieldキーワードを呼び出すことにより、反復処理を(だけでなく)カスタマイズできます。あなたはここでジェネレータ/イテレータの違いを再生して見ることができます:

デモbabel/repl

17

1つの問題は、yieldが関数の呼び出し元に1レベルしか与えないことです。したがって、コールバックでyieldを実行すると、想定したとおりに動作しない場合があります。

_// The following yield:
function *upper (items) { // <---- does not yield here
  items.map(function (item) { // <----- instead it yields here
    try {
      yield item.toUpperCase()
    } catch (e) {
      yield 'null'
    }
  }
}
_

したがって、上記のコードでは、生成された値にまったくアクセスできません。 _Array.prototype.map_は、生成された値にアクセスできます。そして、あなたが.map()のコードを書いた人なら、あなたはその値を得ることができます。しかし、あなたは_Array.prototype.map_を書いた人ではなく、_Array.prototype.map_を書いた人は生成された値を再生成しないので、生成された値にアクセスできませんまったく(そしてうまくいけば、それらはすべてガベージコレクションされます)。

それを機能させることはできますか?

コールバックでyieldを機能させることができるかどうか見てみましょう。ジェネレーターの.map()のように動作する関数を書くことができるでしょう:

_// WARNING: UNTESTED!
function *mapGen (arr,callback) {
    for (var i=0; i<arr.length; i++) {
        yield callback(arr[i])
    }
}
_

その後、次のように使用できます。

_mapGen(items,function (item) {
    yield item.toUpperCase();
});
_

または、勇気がある場合は_Array.prototype_を拡張できます。

_// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
    for (var i=0; i<this.length; i++) {
        yield callback(this[i])
    }
};
_

おそらく次のように呼び出すことができます。

_function *upper (items) {
  yield* items.mapGen(function * (item) {
    try {
      yield item.toUpperCase()
    } catch (e) {
      yield 'null'
    }
  })
}
_

2回譲る必要があることに注意してください。これは、内部のyieldがmapGenに戻るため、mapGenはその値を生成し、upperからその値を返すために生成する必要があるためです。

OK。この種の作品ですが、完全ではありません:

_var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
_

私たちが望むものとは正確には異なります。しかし、最初の利回りは利回りを返すので、それはある程度理にかなっています。それで、各イールドをジェネレーターオブジェクトとして処理しますか?どれどれ:

_var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value.next().value.next().value); // works
console.log(u.next().value.next().value.next().value); // doesn't work
_

OK。 2番目の呼び出しが機能しない理由を理解しましょう。

上の関数:

_function *upper (items) {
  yield* items.mapGen(/*...*/);
}
_

mapGen()の戻り値を返します。ここでは、mapGenの機能を無視して、yieldが実際に何を意味するかについて考えてみましょう。

したがって、最初に.next()を呼び出すと、関数はここで一時停止されます。

_function *upper (items) {
  yield* items.mapGen(/*...*/); // <----- yields value and paused
}
_

これは最初のconsole.log()です。 2回目に.next()を呼び出すと、yieldの後の行で関数呼び出しが続行されます。

_function *upper (items) {
  yield* items.mapGen(/*...*/);
  // <----- function call resumes here
}
_

これは(その行にyieldキーワードがないためyieldではなく)何も返しません(未定義)。

これが、2番目のconsole.log()が失敗する理由です。*upper()関数は、生成するオブジェクトが不足しています。実際、生成されるのは1回だけなので、生成するオブジェクトは1つだけです。値を1つだけ生成するジェネレータです。

OK。したがって、次のようにすることができます。

_var u = upper(['aaa','bbb','ccc']);
var uu = u.next().value; // the only value that upper will ever return
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
_

わーい!しかし、これが事実である場合、コールバックの最も内側のyieldはどのように機能しますか?

よく考えてみると、コールバックの最も内側のyield*upper()yieldのように動作することに気付くでしょう-それは常に1つの値しか返しません。しかし、それを2回以上使用することはありません。これは、2回目にuu.next()を呼び出したとき、同じコールバックではなく、1つの値しか返さない別のコールバックを返すためです。

だからそれは動作します。またはそれを動作させることができます。しかし、それは一種の愚かです。

結論:

結局、yieldが期待どおりに機能しない理由を理解するための重要なポイントは、yieldがコードの実行を一時停止し、次の行で実行を再開することです。収量がなくなると、ジェネレータは終了します(is _.done_)。

認識すべき2番目のポイントは、コールバックとそれらのすべてのArrayメソッド(_.map_、_.forEach_など)は魔法ではないということです。それらは単なるJavaScript関数です。そのため、それらをforwhileのような制御構造と考えるのは少し誤りです。

エピローグ

mapGenをきれいに動作させる方法があります:

_function upper (items) {
  return items.mapGen(function (item) {
    try {
      return item.toUpperCase()
    } catch (e) {
      return 'null'
    }
  })
}
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value);
console.log(u.next().value);
console.log(u.next().value);
_

ただし、この場合、コールバックからフォームが返され(yieldではありません)、フォームupperも返されます。したがって、このケースはforループ内のyieldに展開されますが、これはここでは説明していません。

15
slebetman

「co-npm」によって別の方法を使用できます:co.wrap(fn *)

function doSomething(){
    return new promise()
}

var fn = co.wrap(function* (arr) {
  var data = yield arr.map((val) => {
    return doSomething();
});
return data;
});
fn(arr).then(function (val) {
 consloe.log(val)
});
0