web-dev-qa-db-fra.com

Comment faire en sorte qu'une fonction attende qu'un rappel ait été appelé avec node.js

J'ai une fonction simplifiée qui ressemble à ceci:

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

En gros, je veux qu’elle appelle myApi.exec et renvoie la réponse donnée dans le rappel lambda. Cependant, le code ci-dessus ne fonctionne pas et retourne simplement immédiatement.

Juste pour une tentative très furtive, j’ai essayé le dessous qui n’a pas fonctionné, mais au moins vous avez une idée de ce que j’essaie d’atteindre:

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

Fondamentalement, quel est le bon moyen d’agir en ce sens? Je veux que ma fonction attende que le rappel soit appelé, puis renvoie la valeur qui lui a été transmise.

239
Chris

Le "bon noeud.js/événement conduit" moyen de faire ceci est de pas attendre.

Comme presque tout le reste lorsque vous travaillez avec des systèmes événementiels tels que node, votre fonction doit accepter un paramètre de rappel qui sera appelé à la fin du calcul. L'appelant ne doit pas attendre que la valeur soit "renvoyée" dans le sens normal, mais plutôt envoyer la routine qui gérera la valeur résultante:

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

Donc, vous ne l'utilisez pas comme ça:

var returnValue = myFunction(query);

Mais comme ça:

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

vérifiez ceci: https://github.com/luciotato/waitfor-ES6

votre code avec wait.for: (nécessite des générateurs, drapeau --harmony)

function* (query) {
  var r = yield wait.for( myApi.exec, 'SomeCommand');
  return r;
}
23
Lucio M. Tato

Une façon d'y parvenir consiste à intégrer l'appel API dans une promesse, puis à utiliser await pour attendre le résultat.

// 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();

Sortie:

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

Si vous ne souhaitez pas utiliser le rappel, vous pouvez utiliser le module "Q".

Par exemple:

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. 

});

Pour plus d'informations, consultez ceci: https://github.com/kriskowal/q

10
vishal patel

Si vous voulez que ce soit très simple et facile, pas de bibliothèques sophistiquées, d'attendre que les fonctions de rappel soient exécutées dans le noeud, avant d'exécuter un autre code, c'est comme ça:

//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();
}
9
Marquinho Peli

Remarque: cette réponse ne devrait probablement pas être utilisée dans le code de production. C'est un hack et vous devriez connaître les implications.

Il existe le module vrun (mis à jour pour les versions plus récentes de Nodejs ici ) dans lequel vous pouvez exécuter une boucle unique de la boucle d'événement principal libuv (qui est la boucle principale de Nodejs).

Votre code ressemblerait à ceci:

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

(Vous pouvez également utiliser uvrun.runNoWait(). Cela pourrait éviter certains problèmes de blocage, mais cela prend 100% de la CPU.)

Notez que cette approche invalide l’objet de Nodejs, c’est-à-dire que tout est asynchrone et non bloquant. En outre, cela pourrait augmenter considérablement la profondeur de votre pile d'appel et vous pourriez vous retrouver avec des débordements de pile. Si vous exécutez une telle fonction de manière récursive, vous aurez certainement des problèmes.

Voir les autres réponses sur la façon de redéfinir votre code pour le faire "correctement".

Cette solution ici n’est probablement utile que lorsque vous testez et esp. veulent avoir synchronisé et le code de série.

5
Albert

Depuis le noeud 4.8.0, vous pouvez utiliser la fonction de générateur appelée ES6. Vous pouvez suivre ceci article pour des concepts plus profonds. Mais fondamentalement, vous pouvez utiliser des générateurs et des promesses pour faire ce travail. J'utilise bluebird pour promisify et gérer le générateur.

Votre code devrait bien fonctionner comme dans l'exemple ci-dessous.

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));
4
Douglas Soares

Pour moi, cela a fonctionné

JSON.parse(result)['key']

sur le résultat que j'attendais. Ce n'est peut-être pas "super général", mais à la fin "JSON.parse" gérait l'attente de l'appel asynchrone, alors que result['key'] ne le faisait pas.

1
MrMuc Mic

en supposant que vous ayez une fonction:

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


};

vous pouvez utiliser des callbacks comme ceci:

fetchPage(1, x = function(next, page) {
if (next) {
    console.log("^^^ CALLBACK -->  fetchPage: " + page);
    fetchPage(page, x);
}
});
1
Z0LtaR
exports.dbtest = function (req, res) {
  db.query('SELECT * FROM users', [], res, renderDbtest);
};

function renderDbtest(result, res){
  res.render('db', { result: result });
}

Voici comment je l'ai fait, il vous suffit de passer "res" avec elle, afin que vous puissiez rendre plus tard

0
harmony

Cela va à l'encontre du but de ne pas bloquer IO - vous le bloquez quand il n'a pas besoin d'être bloqué :)

Vous devez imbriquer vos rappels au lieu d'obliger node.js à attendre ou appeler un autre rappel à l'intérieur du rappel où vous avez besoin du résultat de r.

Si vous devez forcer le blocage, il y a de fortes chances que vous pensiez que votre architecture est mauvaise.

0
user193476