Bir işlevi node.js kullanarak geri arama çağrılıncaya kadar bekletme


266

Ben böyle görünüyor basitleştirilmiş bir işlevi var:

function(query) {
  myApi.exec('SomeCommand', function(response) {
    return response;
  });
}

Temelde ben aramak myApi.execve geri arama lambda verilen yanıtı dönmek istiyorum . Ancak, yukarıdaki kod çalışmaz ve hemen geri döner.

Sadece çok acayip bir girişim için, işe yaramayan aşağıda denedim, ama en azından ne yapmaya çalıştığım fikrini alıyorsunuz:

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  while (!r) {}
  return r;
}

Temel olarak, bununla ilgili iyi bir 'node.js / olay güdümlü' yol nedir? İşlevimin geri arama çağrılıncaya kadar beklemesini ve ardından kendisine iletilen değeri döndürmesini istiyorum.


3
Yoksa burada tamamen yanlış bir şekilde mi gidiyorum ve bir yanıt döndürmek yerine başka bir geri arama mı çağırmalıyım?
Chris

Bu bence meşgul döngü neden çalışmıyor en iyi SO açıklamasıdır .
bluenote10

Beklemeye çalışmayın. Geri arama işleminin sonunda bir sonraki işlevi (geri aramaya bağımlı) arayın
Atul

Yanıtlar:


282

Bunu yapmanın "iyi node.js / olay güdümlü" yolu beklemeyin .

Düğüm gibi olay güdümlü sistemlerle çalışırken hemen hemen her şey gibi, işleviniz de hesaplama tamamlandığında çağrılacak bir geri arama parametresini kabul etmelidir. Arayan, değerin normal anlamda "döndürülmesini" beklememeli, sonuçta elde edilen değeri işleyecek rutini göndermelidir:

function(query, callback) {
  myApi.exec('SomeCommand', function(response) {
    // other stuff here...
    // bla bla..
    callback(response); // this will "return" your value to the original caller
  });
}

Yani böyle kullanmıyorsunuz:

var returnValue = myFunction(query);

Ama böyle:

myFunction(query, function(returnValue) {
  // use the return value here instead of like a regular (non-evented) return value
});

5
Tamam harika. MyApi.exec geri aramayı hiç çağırmazsa ne olur? Nasıl geri arama bizim ya da bir şey söyleyerek bir hata değeri ile 10 saniye demek sonra geri çağırmak nasıl yapabilirim?
Chris

5
Veya daha iyisi (geri arama iki kez çağrılamaz
Jakob

148
Düğüm / js'de engellemenin standart olmadığı açıktır, ancak engellemenin istendiği zaman kesinlikle vardır (örneğin stdin üzerinde engelleme). Düğümün bile "engelleme" yöntemleri vardır (tüm fs sync*yöntemlere bakın). Bu yüzden bence bu hala geçerli bir soru. Yoğun beklemenin yanı sıra düğümde engelleme elde etmenin güzel bir yolu var mı?
nategood

7
@Nategood'un yorumuna geç cevap: Birkaç yol düşünebilirim; Bu yorumda açıklamak için çok fazla, ama google. Düğümün bloke edilmediğini unutmayın, bu yüzden bunlar mükemmel değildir. Onları öneri olarak düşünün. Her neyse, işte gidiyor: (1) Fonksiyonunuzu uygulamak için C'yi kullanın ve onu kullanmak için NPM'de yayınlayın. Yani ne syncyöntemler yapmak. (2) Elyafları kullanın, github.com/laverdet/node-fibers , (3) Vaatler kullanın, örneğin Q-kütüphanesi, (4) Javascript'in üstünde, engelleme gibi görünen, ancak async'i derleyen, gibi maxtaco.github.com/coffee-script
Jakob

106
İnsanlar "bunu yapmamalısın" sorusuna cevap vermek çok sinir bozucu. Eğer biri yardımcı olmak ve bir soruyu cevaplamak istiyorsa, bu yapılacak bir şeydir. Ama bana açıkça bir şey yapmamam gerektiğini söylemek sadece düşmanca. Birinin bir rutini eşzamanlı veya eşzamansız olarak çağırmak istemesinin milyonlarca farklı nedeni vardır. Bu nasıl yapılacağı ile ilgili bir soruydu. Cevabı verirken API'nin doğası hakkında yararlı tavsiyeler verirseniz, bu yardımcı olur, ancak bir cevap vermezseniz, neden cevap vermeyi zahmete uğratın. (Sanırım gerçekten kendi tavsiyeme
yön

46

Bunu başarmanın bir yolu, API çağrısını bir söze sarmak ve ardından awaitsonucu beklemek için kullanmaktır .

// let's say this is the API function with two callbacks,
// one for success and the other for error
function apiFunction(query, successCallback, errorCallback) {
    if (query == "bad query") {
        errorCallback("problem with the query");
    }
    successCallback("Your query was <" + query + ">");
}

// myFunction wraps the above API call into a Promise
// and handles the callbacks with resolve and reject
function apiFunctionWrapper(query) {
    return new Promise((resolve, reject) => {
        apiFunction(query,(successResponse) => {
            resolve(successResponse);
        }, (errorResponse) => {
            reject(errorResponse)
        });
    });
}

// now you can use await to get the result from the wrapped api function
// and you can use standard try-catch to handle the errors
async function businessLogic() {
    try {
        const result = await apiFunctionWrapper("query all users");
        console.log(result);

        // the next line will fail
        const result2 = await apiFunctionWrapper("bad query");
    } catch(error) {
        console.error("ERROR:" + error);
    }
}

// call the main function
businessLogic();

Çıktı:

Your query was <query all users>
ERROR:problem with the query

Bu, bir işlevi geri arama ile sarmalamanın çok iyi yapılmış bir örneğidir, async/await bu yüzden sık sık buna ihtiyacım yok, bu durumu nasıl ele alacağımı hatırlamakta sorun yaşıyorum, kişisel notlarım / referanslarım için bunu kopyalıyorum.
robert arles


10

Geri aramak istemiyorsanız "Q" modülünü kullanabilirsiniz.

Örneğin:

function getdb() {
    var deferred = Q.defer();
    MongoClient.connect(databaseUrl, function(err, db) {
        if (err) {
            console.log("Problem connecting database");
            deferred.reject(new Error(err));
        } else {
            var collection = db.collection("url");
            deferred.resolve(collection);
        }
    });
    return deferred.promise;
}


getdb().then(function(collection) {
   // This function will be called afte getdb() will be executed. 

}).fail(function(err){
    // If Error accrued. 

});

Daha fazla bilgi için şu adrese bakın: https://github.com/kriskowal/q


9

Çok basit ve kolay olmasını istiyorsanız, süslü kütüphaneler yok, başka bir kod çalıştırmadan önce düğümde geri çağrı işlevlerinin yürütülmesini beklemek şu şekildedir:

//initialize a global var to control the callback state
var callbackCount = 0;
//call the function that has a callback
someObj.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});
someObj2.executeCallback(function () {
    callbackCount++;
    runOtherCode();
});

//call function that has to wait
continueExec();

function continueExec() {
    //here is the trick, wait until var callbackCount is set number of callback functions
    if (callbackCount < 2) {
        setTimeout(continueExec, 1000);
        return;
    }
    //Finally, do what you need
    doSomeThing();
}

5

Not: Bu cevap muhtemelen üretim kodunda kullanılmamalıdır. Bu bir hack ve sonuçları hakkında bilmeniz gerekir.

Orada uvrun (daha yeni Nodejs sürümleri için güncelleştirilmiş modül burada sen (Nodejs ana döngü) libuv ana olay döngünün tek döngü yuvarlak yürütebilirsiniz).

Kodunuz şöyle görünecektir:

function(query) {
  var r;
  myApi.exec('SomeCommand', function(response) {
    r = response;
  });
  var uvrun = require("uvrun");
  while (!r)
    uvrun.runOnce();
  return r;
}

(Alternatif olarak kullanabilirsiniz uvrun.runNoWait(). Bu, engelleme ile ilgili bazı sorunları önleyebilir, ancak% 100 CPU alır.)

Bu yaklaşımın Nodejs'in tüm amacını geçersiz kıldığını, yani her şeyin zaman uyumsuz ve engellememesini geçersiz kıldığını unutmayın. Ayrıca, çağrı saldırısı derinliğinizi çok artırabilir, böylece yığın taşmaları ile sonuçlanabilir. Eğer böyle bir işlevi tekrar tekrar çalıştırırsanız, kesinlikle sıkıntılarla karşılaşacaksınız.

Kodunuzu "doğru" yapmak için nasıl yeniden tasarlayacağınızla ilgili diğer yanıtlara bakın.

Buradaki çözüm muhtemelen sadece test ve esp yaparken faydalıdır. senkronize edilmiş ve seri kodlu olmak istiyorum.


5

4.8.0 düğümünden bu yana ES6 jeneratörü adı verilen özelliği kullanabilirsiniz. Daha derin kavramlar için bu makaleyi takip edebilirsiniz . Ama temelde bu işi yapmak için jeneratörleri ve sözleri kullanabilirsiniz. Jeneratörü vaat etmek ve yönetmek için mavi kuş kullanıyorum .

Kodunuz aşağıdaki örnekte olduğu gibi iyi olmalıdır.

const Promise = require('bluebird');

function* getResponse(query) {
  const r = yield new Promise(resolve => myApi.exec('SomeCommand', resolve);
  return r;
}

Promise.coroutine(getResponse)()
  .then(response => console.log(response));

1

varsayalım ki bir fonksiyonunuz var:

var fetchPage(page, callback) {
   ....
   request(uri, function (error, response, body) {
        ....
        if (something_good) {
          callback(true, page+1);
        } else {
          callback(false);
        }
        .....
   });


};

bunun gibi geri aramalardan yararlanabilirsiniz:

fetchPage(1, x = function(next, page) {
if (next) {
    console.log("^^^ CALLBACK -->  fetchPage: " + page);
    fetchPage(page, x);
}
});

-1

Bu, engellemeyen IO'nun amacını yener - engelleme gerektirmediğinde engelliyorsunuz :)

Node.js'yi beklemeye zorlamak yerine geri aramalarınızı iç içe yerleştirmeli veya geri aramanın içinde, sonuca ihtiyacınız olan başka bir geri aramayı çağırmalısınız r.

Büyük olasılıkla engellemeyi zorlamanız gerekiyorsa, mimarinizi yanlış düşünüyorsunuzdur.


Bunu geriye doğru çektiğimden şüphe duydum.
Chris

31
Şansım, sadece http.get()bazı URL ve console.log()içeriğine hızlı bir komut dosyası yazmak istiyorum . Düğümde bunu yapmak için neden geriye atlamam gerekiyor?
Dan Dascalescu

6
@DanDascalescu: Peki neden statik dillerde yapmak için tip imzaları bildirmem gerekiyor? Ve neden C benzeri dillerde bir ana yönteme koymak zorundayım? Ve bunu neden derlenmiş bir dilde derlemeliyim? Sorguladığınız şey Node.js'de temel bir tasarım kararıdır. Bu kararın artıları ve eksileri var. Hoşunuza gitmiyorsa, tarzınıza daha uygun başka bir dil kullanabilirsiniz. Bu yüzden birden fazla var.
Jakob

@ Jakob: listelediğiniz çözümler gerçekten yetersizdir. Bu, Meteor'un elyafta Düğümün sunucu tarafında kullanımı gibi iyi olmayanların olmadığı anlamına gelmez, bu da geri arama cehennemi problemini ortadan kaldırır.
Dan Dascalescu

13
@ Jakob: "Ekosistem X neden ortak görevi Y gereksiz yere zorlaştırıyor?" "eğer beğenmezseniz, ekosistem X'i kullanmayın", o zaman bu, ekosistem X tasarımcılarının ve bakımcılarının ekosistemlerinin gerçek kullanılabilirliğinin üzerinde kendi egolarını önceliklendirdiklerinin güçlü bir işaretidir. Düğüm topluluğunun (Ruby, Elixir ve hatta PHP topluluklarının aksine) ortak görevleri zorlaştırmak için kendi yolumdan gittiği deneyimim oldu. Kendinizi bu karşıtlığın canlı bir örneği olarak sunduğunuz için ÇOK teşekkür ederim.
Caz

-1

Zaman uyumsuzluk ve beklemek çok daha kolaydır.

router.post('/login',async (req, res, next) => {
i = await queries.checkUser(req.body);
console.log('i: '+JSON.stringify(i));
});

//User Available Check
async function checkUser(request) {
try {
    let response = await sql.query('select * from login where email = ?', 
    [request.email]);
    return response[0];

    } catch (err) {
    console.log(err);

  }

}

Soruda kullanılan API bir söz vermiyor, bu yüzden ilk önce bir tane sarmanız gerekiyor… bu cevabın iki yıl önce yaptığı gibi .
Quentin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.