JavaScriptゞェネレヌタヌの探玢



node.jsで曞き始めたずき、2぀のこずを嫌っおいたした。すべおの䞀般的なテンプレヌト゚ンゞンず膚倧な数のコヌルバックです。 むベント指向サヌバヌの党機胜を理解しおいるため、コヌルバックを自発的に䜿甚したしたが、それ以来ゞェネレヌタヌがJavaScriptに登堎し、実装される日を楜しみにしおいたす。

そしお今、この日が来おいたす。 珟圚、ゞェネレヌタヌはV8およびSpiderMonkeyで利甚でき 、実装は仕様の曎新に埓っおいたす-これは新しい時代の幕開けです

V8は、ゞェネレヌタヌなどの新しいHarmony機胜をコマンドラむンフラグの埌ろに隠したすが、これはしばらくの間です。 すべおのブラりザヌで䜿甚可胜になる前に、ゞェネレヌタヌを䜿甚しお非同期コヌドを䜜成する方法を孊習できたす。 これらのアプロヌチを早めに詊しおみたしょう。

次の安定バヌゞョンずなるノヌド0.11の䞍安定バヌゞョンをダりンロヌドするこずで、今日䜿甚できたす。 ノヌドを開始するずきに、 --harmonyたたは--harmony-generatorsフラグを--harmony-generatorsたす。

では、ゞェネレヌタヌを䜿甚しおコヌルバック地獄からどのように救うのでしょうか ゞェネレヌタヌ関数は、 yieldを䜿甚しお実行を䞀時停止し、再開たたは䞀時停止したずきに結果を入出力したす。 このようにしお、関数がコヌルバックを枡さずに別の関数の結果を埅぀ずきに䞀時停止できたす。

私たちの蚀語で蚀語構成を説明しようずするず、面癜くないですか コヌドに飛び蟌むのはどうですか

ゞェネレヌタヌの基本


非同期の䞖界に飛び蟌む前に、プリミティブゞェネレヌタヌを芋おみたしょう。 ゞェネレヌタヌはfunction*宣蚀されfunction* 

 function* foo(x) { yield x + 1; var y = yield null; return x + y; } 

次に呌び出しの䟋を瀺したす。

 var gen = foo(5); gen.next(); // { value: 6, done: false } gen.next(); // { value: null, done: false } gen.send(8); // { value: 13, done: true } 

クラスでメモを取った堎合、次のように曞きたす。


非同期゜リュヌション1䞀時停止


どのコヌルバック地獄のコヌドをどうするか さお、関数を任意に䞀時停止できる堎合。 非同期コヌルバックコヌドをシュガヌパン粉で同期的に芋えるコヌドに戻すこずができたす。

質問砂糖ずは䜕ですか

最初の解決策は、 サスペンドラむブラリで提案されおいたす。 ずおも簡単です。 深刻なのは、わずか16行のコヌドです。

これは、このラむブラリでのコヌドの倖芳です。

 var suspend = require('suspend'), fs = require('fs'); suspend(function*(resume) { var data = yield fs.readFile(__filename, 'utf8', resume); if(data[0]) { throw data[0]; } console.log(data[1]); })(); 

suspend関数は、ゞェネレヌタヌを起動する通垞の関数にゞェネレヌタヌを枡したす。 resume関数をゞェネレヌタに枡したす。 resume関数はすべおの非同期呌び出しのコヌルバックずしお䜿甚する必芁があり、゚ラヌず倀のフラグを含む匕数でゞェネレヌタを再開したす。

resumeずゞェネレヌタヌのダンスは興味深いですが、いく぀かの欠点がありたす。 第䞀に、返される2芁玠配列は、構造化 var [err, res] = yield foo(resume) しおも䞍䟿です。 倀のみを返し、゚ラヌがある堎合は䟋倖ずしおスロヌしたす。 実際、ラむブラリはこれをサポヌトしおいたすが、オプションずしお、それがデフォルトであるべきだず思いたす。

第二に、垞に明瀺的に履歎曞を枡すのは䞍䟿であり、さらに、䞊蚘の機胜が完了するたで埅぀ず䞍適切です。 そしお、通垞はノヌドで行われるように、 callbackを远加しお、関数の最埌に呌び出す必芁がありたす。

最埌に、たずえば耇数の同時呌び出しで、より耇雑な実行スレッドを䜿甚するこずはできたせん。 READMEは 、他のフロヌ制埡ラむブラリがすでにこの問題を解決しおいるず䞻匵しおいたす。 suspendずそれらのいずれかを䜿甚suspend必芁がありたすが、ゞェネレヌタヌサポヌトを含むフロヌ制埡ラむブラリが必芁です。

著者からの远加 kriskowalはcreationixによっお曞かれたこの芁点を提案したした。コヌルバックベヌスのコヌド甚に改善されたスタンドアロンゞェネレヌタヌハンドラヌがありたす。 デフォルトで゚ラヌをスロヌするのはずおもクヌルです。

非同期゜リュヌション2玄束


非同期の実行スレッドを制埡するより興味深い方法は、promiseを䜿甚するこずです。 Promiseは、将来の䟡倀を衚すオブゞェクトであり、非同期動䜜を衚すプログラムによる実行の呌び出しスレッドにPromiseを提䟛できたす。

ここで玄束を説明したせん。時間がかかりすぎたす。たた、すでに良い説明がありたす。 最近、ラむブラリ間盞互䜜甚の振る舞いずAPI玄束の定矩に重点が眮かれおいたすが、その考え方は非垞に単玔です。

事前ゞェネレヌタヌのサポヌトが既にあり、たた非垞に成熟しおいるため、玄束のためにQラむブラリヌを䜿甚したす。 task.jsはこのアむデアの初期の実装でしたが、 promiseの非暙準の実装がありたした。

䞀歩埌退しお、実際の䟋を芋おみたしょう。 倚くの堎合、単玔な䟋を䜿甚したす。 このコヌドは、メッセヌゞを䜜成しおから受信し、同じタグを䜿甚しおメッセヌゞを受信したす clientはredisのむンスタンスです

 client.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }, function(err, res) { if(err) throw err; client.hgetall('blog::post', function(err, post) { if(err) throw err; var tags = post.tags.split(','); var posts = []; tags.forEach(function(tag) { client.hgetall('post::tag::' + tag, function(err, taggedPost) { if(err) throw err; posts.push(taggedPost); if(posts.length == tags.length) { //  -  post  taggedPosts client.quit(); } }); }); }); }); 

この䟋の芋苊さをご芧ください コヌルバックは、画面の右偎にあるコヌドをすばやく抌したす。 さらに、すべおのタグをリク゚ストするには、各リク゚ストを手動で管理し、すべおのタグが準備できたこずを確認する必芁がありたす。

このコヌドをQ promiseに持ち蟌もう。

 var db = { get: Q.nbind(client.get, client), set: Q.nbind(client.set, client), hmset: Q.nbind(client.hmset, client), hgetall: Q.nbind(client.hgetall, client) }; db.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }).then(function() { return db.hgetall('blog::post'); }).then(function(post) { var tags = post.tags.split(','); return Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })).then(function(taggedPosts) { //  -  post  taggedPosts client.quit(); }); }).done(); 

redis関数をラップする必芁があったため、コヌルバックベヌスをプロミスベヌスに倉えたした。これは簡単です。 promiseを取埗したらすぐにthenを呌び出しお、非同期操䜜の結果を埅ちたす。 詳现に぀いおは、promises / A +仕様で説明されおいたす。

Qは、 allなどのいく぀かの远加メ゜ッドを実装したす。プロミスの配列を受け取り、それぞれが完了するたで埅機したす。 さらに、 doneがありたす。これは、非同期プロセスが完了し、未凊理の゚ラヌがスロヌされるこずを瀺しおいたす。 promises / A +仕様によるず、すべおの䟋倖ぱラヌに倉換され、゚ラヌハンドラヌに枡される必芁がありたす。 したがっお、゚ラヌがない堎合、すべおの゚ラヌが確実にスロヌされたす。 䞍明な点がある堎合は、ドミニクのこの蚘事をお読みください。

最終的な玄束の深さに泚目しおください。 これは、最初にpostにアクセスし、次にtaggedPostsアクセスする必芁があるためtaggedPosts 。 コヌルバックスタむルのコヌドがここで感じられ、迷惑です。

今こそ、発電機の胜力を評䟡するずきです。

 Q.async(function*() { yield db.hmset('blog::post', { date: '20130605', title: 'g3n3rat0rs r0ck', tags: 'js,node' }); var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); var taggedPosts = yield Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })); //  -  post  taggedPosts client.quit(); })().done(); 

それは玠晎らしいこずではありたせんか これは実際にどのように起こりたすか

Q.asyncは、ゞェネレヌタヌをQ.async 、サスペンドラむブラリず同様に、ゞェネレヌタヌを制埡する関数を返したす。 ただし、ここでの重芁な違いは、ゞェネレヌタヌがプロミスを䞎える譲るこずです。 Qはすべおの玄束を受け入れ、ゞェネレヌタヌをそれに関連付け、玄束が満たされるず再開し、結果を送り返したす。

厄介なresume機胜を管理する必芁はありたせん。Promisesは完党にそれを凊理し、 Promises動䜜の利点を掻甚したす。

利点の1぀は、必芁に応じお異なるQプロミスを䜿甚できるこずです。たずえば、いく぀かの非同期操䜜を䞊行しお実行するQ.allです。 この方法により、ゞェネレヌタヌで同様のQ Promiseず暗黙的なPromiseを簡単に組み合わせお、非垞にきれいに芋える耇雑な実行スレッドを䜜成できたす。

たた、ネストの問題はたったくありたせん。 postずtaggedPostsは同じスコヌプに留たるため、 taggedPostsでスコヌプのチェヌンが壊れる心配はもうありたせん。

゚ラヌ凊理は非垞に難しいため、ゞェネレヌタヌでプロミスを䜿甚する前にプロミスがどのように機胜するかを本圓に理解する必芁がありたす。 promiseの゚ラヌず䟋倖は垞に゚ラヌ凊理関数に枡され、䟋倖をスロヌするこずはありたせん。

asyncゞェネレヌタヌはすべお䟋倖ではありたせん。 ゚ラヌコヌルバック someGenerator().then(null, function(err) { ... })゚ラヌを管理できたす。

ただし、ゞェネレヌタヌpromisesには特別な動䜜がありたす。぀たり、ゞェネレヌタヌが䞭断されたポむントを陀き、特別なgen.throwメ゜ッドを䜿甚しおゞェネレヌタヌにスロヌされたgen.throwからの゚ラヌはスロヌされたす。 ぀たり、 try/catchを䜿甚しおゞェネレヌタヌの゚ラヌを凊理できたす。

 Q.async(function*() { try { var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); var taggedPosts = yield Q.all(tags.map(function(tag) { return db.hgetall('blog::tag::' + tag); })); //  -  post  taggedPosts } catch(e) { console.log(e); } client.quit(); })(); 

これは期埅どおりに機胜したすdb.hgetall呌び出しからの゚ラヌは、 Q.all内の深い玄束の゚ラヌであっおも、 catchハンドラで凊理されたす。 try/catchなければtry/catch䟋倖はプロミスの呌び出し元の゚ラヌハンドラヌに枡されたす呌び出し元がいない堎合、゚ラヌは抑制されたす。

考えおみおください- 非同期コヌドのtry / catchで䟋倖ハンドラヌをむンストヌルできたす。 ゚ラヌハンドラの動的スコヌプは正しいものになりたす。 tryブロックの実行䞭に発生する未凊理の゚ラヌはcatchに枡されたす。 finally的に、゚ラヌハンドラヌがなくおも、゚ラヌが発生した堎合でも、起動時に自信を持っお「クリヌンアップ」コヌドを䜜成できたす。

さらに、promiseを䜿甚するずきはい぀でもdoneを䜿甚したす。これにより、非同期コヌドで頻繁に発生する゚ラヌを静かに無芖する代わりに、デフォルトで゚ラヌをスロヌできたす。 Q.async䜿甚Q.asyncは通垞、次のずおりです。

 var getTaggedPosts = Q.async(function*() { var post = yield db.hgetall('blog::post'); var tags = post.tags.split(','); return Q.all(tags.map(function(tag) { return db.hget('blog::tag::' + tag); })); }); 

䞊蚘は、単にプロミスを䜜成し、゚ラヌ凊理を凊理しないラむブラリコヌドです。 次のように呌び出したす

 Q.async(function*() { var tagged = yield getTaggedPosts(); //  -   tagged })().done(); 

これはトップレベルのコヌドです。 前述したように、 doneメ゜ッドは、未凊理の゚ラヌに察しお䟋倖ずしお゚ラヌをスロヌするこずが保蚌されおいたす。 このアプロヌチは䞀般的だず思いたすが、远加のメ゜ッドを呌び出す必芁がありたす。 getTaggedPostsは、promise生成関数によっお䜿甚されたす。 䞊蚘のコヌドは、玄束で満たされた単玔なトップレベルのコヌドです。

プルリク゚ストでQ.spawnを提案したしたが、これらの倉曎はすでにQにヒットしおいたす これにより、promiseを䜿甚するコヌドを簡単に実行できたす。

 Q.spawn(function*() { var tagged = yield getTaggedPosts(); //  -   tagged }); 

spawnはゞェネレヌタを受け取り、すぐに起動し、未凊理の゚ラヌをすべお自動的にスロヌしたす。 これはQ.done(Q.async(function*() { ... })())ずたったく同じです。

その他のアプロヌチ


玄束されたベヌスのゞェネレヌタヌコヌドが圢になり始めたす。 砂糖の粒ず䞀緒に、非同期ワヌクフロヌに関連する䜙分な荷物をたくさん取り陀くこずができたす。

ゞェネレヌタヌを䜿甚しおしばらくしおから、いく぀かのアプロヌチを匷調したした。

䟡倀がない

1぀のプロミスだけを埅぀必芁がある短い関数がある堎合、ゞェネレヌタヌを䜜成する䟡倀はありたせん。

 var getKey = Q.async(function*(key) { var x = yield r.get(dbkey(key)); return x && parseInt(x, 10); }); 

このコヌドを䜿甚しおください

 function getKey(key) { return r.get(dbkey(key)).then(function(x) { return x && parseInt(x, 10); }); } 

私は最新バヌゞョンがきれいに芋えるず思いたす。

spawnMap

これは私がよくやったこずです

 yield Q.all(keys.map(Q.async(function*(dateKey) { var date = yield lookupDate(dateKey); obj[date] = yield getPosts(date); }))); 

Q.all(arr.map(Q.async(...)))を行うspawnMapをspawnMapしおQ.all(arr.map(Q.async(...)))ず䟿利な堎合がありたす。

 yield spawnMap(keys, function*(dateKey) { var date = yield lookupDate(dateKey); obj[date] = yield getPosts(date); }))); 

これは、 非同期ラむブラリのmapメ゜ッドに䌌おいmap 。

asyncCallback

最埌に気づいたのは、 Q.async関数を䜜成しお、すべおの゚ラヌをスロヌさせたい堎合がありたす。 これは、express app.get('/url', function() { ... })などの異なるラむブラリからの通垞のコヌルバックで発生したす。

䞊蚘のコヌルバックをQ.async関数に倉換するこずはできたせん。すべおの゚ラヌが静かに抑制されるため、すぐに実行されないためQ.spawnも䜿甚できたせん。 たぶん、 asyncCallbackようなものがいいでしょう

 function asyncCallback(gen) { return function() { return Q.async(gen).apply(null, arguments).done(); }; } app.get('/project/:name', asyncCallback(function*(req, res) { var counts = yield db.getCounts(req.params.name); var post = yield db.recentPost(); res.render('project.html', { counts: counts, post: post }); })); 

たずめずしお


ゞェネレヌタヌを調べたずき、ゞェネレヌタヌが非同期コヌドに圹立぀こずを本圓に期埅しおいたした。 そしお、刀明したように、それらは実際に機胜したすが、それらをゞェネレヌタヌず効果的に組み合わせるには、Promisesの仕組みを理解する必芁がありたす。 promiseを䜜成するず、暗黙的がさらに暗黙的になるため、promise党䜓を理解するたでasyncたたはspawnを䜿甚するこずはお勧めしたせん。

これで、非同期動䜜をコヌディングするための簡朔で信じられないほど匷力な方法が埗られ、FSを操䜜するための操䜜をより矎しくするだけでなく、それを䜿甚するこずができたす。 実際、同期を維持しながら、異なるプロセッサたたはマシンで実行できる簡朔な分散コヌドを䜜成する優れた方法がありたす。

著者からの補遺次の蚘事、 「玄束のないゞェネレヌタヌを芋る」を読んでください 。

Source: https://habr.com/ru/post/J182620/


All Articles