Generator di KOA

Bagaimana cara kerja aplikasi digunakan di KOA? Saat saya menyetel beberapa generator di dalam app.use, semuanya berfungsi dengan sempurna. Bagaimana saya bisa melakukan hal yang sama di tempat lain?

Ketika saya baru saja menjalankan manual generator:

var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}
console.log({'outside: ': getRelationsList().next()});
getRelationsList().next();

Saya baru saja menyelesaikan { 'luar: ': { nilai: [Fungsi], selesai: salah } }

Ini yang saya harapkan adalah:

{ 'outside: ': { value: {object_with_results}, done: false } }
{ 'inside: ': {object_with_results}

EDIT

Saya mengubah kode saya seperti itu:

var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}
console.log({'outside ': co(getRelationsList)});

Sekarang di dalam log konsol tunjukkan saya hasil yang bagus tetapi di luar log konsol hanya menunjukkan objek kosong. masukkan deskripsi gambar di sini


person Oskar Kamiński    schedule 12.05.2015    source sumber


Jawaban (3)


Generator adalah alat yang ampuh untuk mengatur kode asinkron, tetapi generator tidak secara ajaib menunggu kode asinkron dijalankan.

Apa yang terjadi

Mari kita telusuri kode Anda sehingga Anda dapat melihat apa yang terjadi:

getRelationsList adalah fungsi generator, yang ketika dipanggil akan mengembalikan generator baru. Pada titik ini, tidak ada kode dalam fungsi generator Anda yang dipanggil (walaupun jika Anda meneruskan params, kode tersebut akan disetel). Anda kemudian memanggil .next pada generator Anda untuk memulai eksekusi fungsi generator. Ini akan dijalankan hingga mencapai pernyataan hasil pertama dan mengembalikan objek dengan nilai yang dihasilkan dan status penyelesaian generator.

Tampaknya Anda memahami sebagian besar dari hal itu sejauh ini, tetapi generator tidak secara ajaib mengubah nilai yang dihasilkan. Saat Anda menghasilkan db.relations.find({}), Anda akan mendapatkan nilai kembalian dari fungsi find yang saya asumsikan adalah Janji atau sejenisnya:

jadi nilai 'luar' Anda adalah { value:Promise, done:false }

Alasan mengapa console.log bagian dalam Anda tidak pernah berjalan adalah karena Anda sebenarnya membuat generator baru setiap kali Anda memanggil getRelationsList(), jadi ketika Anda memanggil getRelationsList().next() lagi setelah bagian luar console.log Anda membuat generator baru dan memanggil berikutnya, sehingga hanya dijalankan hingga hasil pertama, sama seperti panggilan pada baris sebelumnya.

Untuk menyelesaikan eksekusi, Anda harus memanggil next dua kali pada instance generator yang sama: sekali untuk mengeksekusi hingga hasil dan sekali untuk melanjutkan eksekusi hingga akhir fungsi.

var gen = getRelationsList()
gen.next() // { value:Promise, done:false }
gen.next() // { value:undefined, done:true } (will also console.log inside)

Anda akan melihat, namun jika Anda menjalankan ini, bagian dalam console.log akan menjadi undefined. Itu karena nilai pernyataan yield sama dengan nilai yang diteruskan ke panggilan .next() berikutnya.

Misalnya:

var gen2 = getRelationsList()
gen2.next() // { value:Promise, done:false }
gen2.next(100) // { value:undefined, done:true } 

Keluaran

{ inside:100 }

Karena kita meneruskan 100 ke panggilan .next() kedua dan itu menjadi nilai pernyataan yield db.relations.find({}) yang kemudian ditetapkan ke res.

Berikut tautan yang mendemonstrasikan semua ini: http://jsfiddle.net/qj1aszub/2/

Solusinya

Pembuat koa menggunakan perpustakaan kecil bernama co yang pada dasarnya menerima janji yang telah dihasilkan dan menunggu hingga janji tersebut selesai sebelumnya meneruskan nilai yang diselesaikan kembali ke fungsi generator (menggunakan fungsi .next()) sehingga Anda dapat menulis kode asinkron dalam gaya sinkron.

co akan mengembalikan janji, yang mengharuskan Anda memanggil metode .then untuk mendapatkan nilai yang dikembalikan dari fungsi generator.

var co = require('co');
var getRelationsList = function *() {
    var res = yield db.relations.find({});
    console.log({'inside: ': res});
    return res
}

co(getRelationsList).then(function(res) {
    console.log({'outside: ': res })
}).catch(function(err){
    console.log('something went wrong')
});

co juga memungkinkan Anda untuk menghasilkan fungsi generator lainnya dan menunggu penyelesaiannya, sehingga Anda tidak perlu menyelesaikan semuanya dengan co di setiap level dan menangani janji hingga Anda berada di 'level teratas':

co(function *() {
    var list = yield getRelationsList()
      , processed = yield processRelations(list)
      , response = yield request.post('/some/api', { data:processed })
    return reponse.data
}).then(function(data) {
    console.log('got:', data)
})
person mlrawlings    schedule 12.05.2015

Masalah Anda adalah Anda memanggil fungsi getRelationsList() beberapa kali dan itu salah.

Ubah kode Anda menjadi berikut

var g = getRelationsList();
console.log('outside: ', g.next());
g.next(); //You get your console.log('inside: .... here
person Molda    schedule 12.05.2015

Generator harus ditindaklanjuti dengan kode luar. Di bawah tenda koa gunakan pustaka co untuk 'Menjalankan' generator.

Inilah cara Anda mencapai apa yang Anda inginkan di luar koa:

var co = require('co');
var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}

co(getRelationsList).catch(function(err){});

Saya melakukan screencast singkat tentang generator JavaScript yang akan membantu Anda memahami apa yang terjadi:

http://knowthen.com/episode-2-understanding-javascript-generators/

++ EDIT

Jika Anda menggunakan generator untuk memprogram dengan gaya yang lebih sinkron (menghilangkan panggilan balik), maka semua pekerjaan Anda harus dilakukan di generator dan Anda harus menggunakan perpustakaan seperti co untuk menjalankan generator.

Berikut adalah contoh yang lebih detail tentang bagaimana Anda berinteraksi dengan generator secara manual. Ini akan membantu Anda memahami hasil yang Anda peroleh.

function * myGenerator () {
  var a = yield 'some value';
  return a;
}
var iterator = myGenerator();
// above line just instantiates the generator
console.log(iterator);
// empty object returned
// {}

var res1 = iterator.next();
// calling next() start the generator to either the 
// first yield statement or to return.
console.log(res1);
// res1 is an object with 2 attributes 
// { value: 'some value', done: false }
// value is whatever value was to the right of the first 
// yield statment 
// done is an indication that the generator hasn't run
// to completion... ie there is more to do
var toReturn = 'Yield returned: ' + res1.value;
var res2 = iterator.next(toReturn);
// calling next(toReturn) passes the value of 
// the variable toReturn as the return of the yield 
// so it's returned to the variable a in the generator 
console.log(res2);
// res2 is an object with 2 attributes 
// { value: 'Yield returned: some value', done: true }
// no further yield statements so the 'value' is whatever
// is returned by the generator.
// since the generator was run to completion 
// done is returned as true
person James Moore    schedule 12.05.2015
comment
Oke. Sekarang saya punya: { 'inside: ': {object_with_results} tetapi masih: { 'outside ': {} } - person Oskar Kamiński; 12.05.2015