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
私の編集者もこれがひどい考えであることを知っています...
免責事項:私は Learn generators Workshopperの作成者です。
@slebetmanの回答は正解で、さらに追加することもできます。
はい、 MDN-反復プロトコル はコールバック内でyield
を直接参照しません。ただし、yield
はgenerators内でのみ使用できるため、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 。
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関数です。そのため、それらをfor
やwhile
のような制御構造と考えるのは少し誤りです。
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
に展開されますが、これはここでは説明していません。
「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)
});