Exportiert asynchrone Knotenmodule

78

Ich habe mich gefragt, was der beste Ansatz für die Konfiguration eines Modulexports ist. "async.function" im folgenden Beispiel kann eine FS- oder HTTP-Anforderung sein, die aus Gründen des Beispiels vereinfacht wurde:

Hier ist ein Beispielcode (asynmodule.js):

var foo = "bar"
async.function(function(response) {
  foo = "foobar";
  // module.exports = foo;  // having the export here breaks the app: foo is always undefined.
});

// having the export here results in working code, but without the variable being set.
module.exports = foo;

Wie kann ich das Modul erst exportieren, wenn der asynchrone Rückruf ausgeführt wurde?

Bearbeiten Sie eine kurze Anmerkung zu meinem tatsächlichen Anwendungsfall: Ich schreibe ein Modul zum Konfigurieren von nconf ( https://github.com/flatiron/nconf ) in einem Rückruf von fs.exists () (dh es analysiert eine Konfigurationsdatei und nconf einrichten).

Brett
quelle
Ich habe mit meinem eigentlichen Anwendungsfall herumgespielt und nconf wird einwandfrei geladen, wenn nconf.file () mit einer nicht vorhandenen Datei aufgerufen wird. Daher benötige ich vorerst keine Lösung. Bin aber trotzdem an dem Ansatz interessiert.
Brett
Ich habe die gleiche Frage, ich möchte ein Versprechen exportieren und requiredie Abhängigkeit asynchron laden. Ich denke das ist mit babel formatierer möglich. Ich denke jedoch keine gute Lösung für diese. :(
Junle Li

Antworten:

65

Ihr Export kann nicht funktionieren, da er sich außerhalb der Funktion befindet, während sich die fooDeklaration innerhalb befindet. Wenn Sie den Export jedoch in das Modul einfügen, können Sie nicht sicher sein, ob der Export definiert wurde.

Der beste Weg, um mit einem Ansync-System zu arbeiten, ist die Verwendung eines Rückrufs. Sie müssen eine Rückrufzuweisungsmethode exportieren, um den Rückruf zu erhalten, und ihn bei der asynchronen Ausführung aufrufen.

Beispiel:

var foo, callback;
async.function(function(response) {
    foo = "foobar";

    if( typeof callback == 'function' ){
        callback(foo);
    }
});

module.exports = function(cb){
    if(typeof foo != 'undefined'){
        cb(foo); // If foo is already define, I don't wait.
    } else {
        callback = cb;
    }
}

Hier async.functionist nur ein Platzhalter, um einen asynchronen Aufruf zu symbolisieren.

In der Hauptsache

var fooMod = require('./foo.js');
fooMod(function(foo){
    //Here code using foo;
});

Mehrfacher Rückrufweg

Wenn Ihr Modul mehrmals aufgerufen werden muss, müssen Sie ein Array von Rückrufen verwalten:

var foo, callbackList = [];
async.function(function(response) {
    foo = "foobar";

    // You can use all other form of array walk.
    for(var i = 0; i < callbackList.length; i++){
        callbackList[i](foo)
    }
});

module.exports = function(cb){
    if(typeof foo != 'undefined'){
        cb(foo); // If foo is already define, I don't wait.
    } else {
        callback.push(cb);
    }
}

Hier async.functionist nur ein Platzhalter, um einen asynchronen Aufruf zu symbolisieren.

In der Hauptsache

var fooMod = require('./foo.js');
fooMod(function(foo){
    //Here code using foo;
});

Versprich Weg

Sie können Promise auch verwenden, um das zu lösen. Diese Methode unterstützt mehrere Aufrufe durch das Design des Versprechens:

var foo, callback;
module.exports = new Promise(function(resolve, reject){
    async.function(function(response) {
        foo = "foobar"

        resolve(foo);
    });
});

Hier async.functionist nur ein Platzhalter, um einen asynchronen Aufruf zu symbolisieren.

In der Hauptsache

var fooMod = require('./foo.js').then(function(foo){
    //Here code using foo;
});

Siehe Promise-Dokumentation

Techniv
quelle
3
Dies würde nicht funktionieren, wenn zwei separate (Haupt-) Dateien diese Funktion aufrufen würden, ohne dass foo bereit wäre, oder? Nur einer ihrer Rückrufe würde ausgelöst, je nachdem, welcher Zeitpunkt der letzte war.
Laggingreflex
In diesem Fall ja. Weil wir keinen Callback-Stack verwalten. Aber es ist einfach, das mit einem Array zu lösen, um den gesamten Rückruf zu speichern.
Techniv
Details: ReferenceError: Async ist nicht definiert
1nstinct
1
Ich habe zwei Fragen: (1) Was ist die Essenz des else-Blocks in Ihrem ersten Beispiel, in dem Sie sagen if(typeof foo != 'undefined'){ cb(foo); // If foo is already define, I don't wait. } else { callback = cb; }. (2) Bedeutet dieser Block, dass requires dieses Modul so lange aufruft, bis es einen Wert ergibt (von seiner asynchronen Reise)? Oder wird davon ausgegangen, dass das Modul während seiner gesamten Lebensdauer nur einen Rückruf erhält, dh nachfolgende Aufrufe können das cbArgument weglassen ?
Ich möchte Antworten
1
@IWantAnswers, in diesem Beispiel kann das Modul von verschiedenen Modulen, die den fooWert verwenden müssen, mehrere Male benötigt werden. Aber Sie wissen nicht, wann es passiert ist. Wenn es also früh ist und der fooWert noch nicht vorhanden ist, speichern Sie die Rückrufe, um auf die Rückgabe des asynchronen Aufrufs zu warten. Am Ende des asynchronen Prozesses werden alle gespeicherten Rückrufe entstapelt und das Array wurde nicht mehr verwendet. Wenn zu diesem Zeitpunkt ein anderes Modul dieses Modul benötigt und abonniert, um den fooWert abzurufen, ist der Wert bereits festgelegt, sodass Sie den Speicher umgehen, um den Rückruf direkt auszuführen.
Techniv
15

Ein ES7-Ansatz wäre eine sofort aufgerufene asynchrone Funktion in module.exports:

module.exports = (async function(){
 //some async initiallizers
 //e.g. await the db module that has the same structure like this
  var db = await require("./db");
  var foo = "bar";

  //resolve the export promise
  return {
    foo
  };
})()

Dies kann mit späterem Warten erforderlich sein:

(async function(){

  var foo = await require("./theuppercode");
  console.log(foo);
})();
Jonas Wilms
quelle
Können Sie den Unterschied / die Auswirkungen zwischen dem Aufrufen und dem Nichtaufrufen erklären?
Bernardo Dal Corno
1
Wenn Sie die Funktion nicht aufrufen, exportieren Sie die Funktion, ohne sie auszuführen.
Jonas Wilms
13

ES6-Antwort mit Versprechungen:

const asyncFunc = () => {
    return new Promise((resolve, reject) => {
        // Where someAsyncFunction takes a callback, i.e. api call
        someAsyncFunction(data => {
            resolve(data)
        })
    })
}

export default asyncFunc

...
import asyncFunc from './asyncFunc'
asyncFunc().then(data => { console.log(data) })

Oder Sie können das Versprechen selbst direkt zurückgeben:

const p = new Promise(...)
export default p
...
import p from './asyncModule'
p.then(...)
Inostia
quelle
1
Dies ist die richtige, moderne Antwort für ES6 und Promises. Danke dafür.
Joshua Pinter
1
Frage: Gibt es einen Grund, warum Sie eine Funktion anstelle der Promisedirekten zurückgeben? Wenn Sie das Promisedirekt zurückgegeben haben, können Sie darauf zugreifen asyncFunc.then(...), oder? Ziemlich neu, also möchte ich deine Meinung einholen.
Joshua Pinter
1
Das würde auch funktionieren. Ich denke, als ich dieses Beispiel schrieb, exportierte ich eine Klasse mit einer asynchronen Methode und formulierte sie so wie eine Funktion. Aber Sie könnten das Versprechen einfach so exportieren: const p = new Promise(...); export default p;und dann in Ihrem Importmodulimport p from '...'; p.then(...);
inostia
Super, danke, dass du das klargestellt hast. Ich nehme an, es ist eine persönliche Präferenz oder gibt es eine Best-Practice-Methode, um die eine oder andere zu verwenden?
Joshua Pinter
Ich denke, es hängt davon ab, ob Sie ein Argument an Ihr asynchrones Modul übergeben müssen, was normalerweise bei mir der Fall ist (z. B. ein idoder andere Parameter). Im ersten Beispiel const asyncFunc = (id) => ...könnten Sie dann idin Ihrer Funktion verwenden. Du würdest es so nennen asyncFunc(id).then(...). Wenn Sie jedoch keine Argumente übergeben müssen, ist es auch in Ordnung, das Versprechen direkt zurückzugeben.
Inostia
10

Ein anderer Ansatz wäre das Umschließen der Variablen in ein Objekt.

var Wrapper = function(){
  this.foo = "bar";
  this.init();
};
Wrapper.prototype.init = function(){
  var wrapper = this;  
  async.function(function(response) {
    wrapper.foo = "foobar";
  });
}
module.exports = new Wrapper();

Wenn der Initialisierer einen Fehler aufweist, erhalten Sie zumindest immer noch den nicht initialisierten Wert, anstatt den Rückruf aufzuhängen.

vangoz
quelle
3
Wie kommt man zu "foo", wenn man das Modul benötigt?
HelpMeStackOverflowMyOnlyHope
var wrapper = require ('wrapper'); console.log (wrapper.foo)
vangoz
9

Sie können auch Versprechen nutzen:

some-async-module.js

module.exports = new Promise((resolve, reject) => {
    setTimeout(resolve.bind(null, 'someValueToBeReturned'), 2000);
});

main.js

var asyncModule = require('./some-async-module');

asyncModule.then(promisedResult => console.log(promisedResult)); 
// outputs 'someValueToBeReturned' after 2 seconds

Dasselbe kann in einem anderen Modul passieren und wird auch wie erwartet gelöst:

in-some-other-module.js

var asyncModule = require('./some-async-module');

asyncModule.then(promisedResult => console.log(promisedResult)); 
// also outputs 'someValueToBeReturned' after 2 seconds

Beachten Sie, dass das Versprechen-Objekt einmal erstellt und dann vom Knoten zwischengespeichert wird. Jeder require('./some-async-module')gibt dieselbe Objektinstanz zurück (in diesem Fall die Versprechen-Instanz).

Efidiles
quelle
0

Andere Antworten schienen Teilantworten zu sein und funktionierten bei mir nicht. Dies scheint etwas vollständig zu sein:

some-module.js

var Wrapper = function(){
  this.callbacks = [];
  this.foo = null;
  this.init();
};
Wrapper.prototype.init = function(){
  var wrapper = this;  
  async.function(function(response) {
    wrapper.foo = "foobar";
    this.callbacks.forEach(function(callback){
       callback(null, wrapper.foo);
    });
  });
}
Wrapper.prototype.get = function(cb) {
    if(typeof cb !== 'function') {
        return this.connection; // this could be null so probably just throw
    }
    if(this.foo) {
        return cb(null, this.foo);
    }
    this.callbacks.push(cb);
}
module.exports = new Wrapper();

main.js

var wrapper = require('./some-module');

wrapper.get(function(foo){
    // foo will always be defined
});

main2.js

var wrapper = require('./some-module');

wrapper.get(function(foo){
    // foo will always be defined in another script
});
tsuz
quelle
Warum hast du callback(null, wrapper.foo);statt callback(wrapper.foo);?
Ich möchte Antworten
@IWantAnswers Das erste Argument ist Fehler und das zweite ist das Ergebnis
Tsuz