Асинхронний експорт модуля nodejs


82

Мені було цікаво, який найкращий підхід для налаштування експорту модуля. "async.function" у прикладі нижче може бути запитом FS або HTTP, спрощеним для прикладу:

Ось приклад коду (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;

Як я можу експортувати модуль лише після того, як було виконано асинхронний зворотний виклик?

відредагуйте коротку примітку щодо мого фактичного випадку використання: я пишу модуль для налаштування nconf ( https://github.com/flatiron/nconf ) у зворотному виклику fs.exists () (тобто він проаналізує конфігураційний файл і встановити nconf).


Погрався з моїм фактичним варіантом використання, і nconf завантажується нормально, якщо nconf.file () викликається з неіснуючим файлом, тому наразі мені не потрібне рішення. Але мене все одно цікавить підхід.
Бретт

У мене таке саме запитання, я хотів би експортувати обіцянку та requireзавантажити залежність асинхронно. Я думаю, що це можливо за допомогою програми Babel Formator. Однак я не думаю, що для них це хороше рішення. :(
Junle Li

Відповіді:


66

Експорт не може працювати, оскільки він знаходиться поза функцією, а fooдекларація знаходиться всередині. Але якщо ви помістите експорт всередину, коли ви використовуєте свій модуль, ви не можете бути впевнені, що експорт був визначений.

Найкращий спосіб роботи з системою ansync - це використання зворотного виклику. Вам потрібно експортувати метод призначення зворотного виклику, щоб отримати зворотний виклик, і викликати його при виконанні асинхронізації.

Приклад:

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;
    }
}

Ось async.functionлише заповнювач, який символізує асинхронний виклик.

В основному

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

Кілька способів зворотного дзвінка

Якщо ваш модуль потрібно викликати більше одного разу, вам потрібно керувати масивом зворотних викликів:

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);
    }
}

Ось async.functionлише заповнювач, який символізує асинхронний виклик.

В основному

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

Обіцянка

Ви також можете використовувати Promise, щоб вирішити це. Цей метод підтримує кілька викликів за проектом Promise:

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

        resolve(foo);
    });
});

Ось async.functionлише заповнювач, який символізує асинхронний виклик.

В основному

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

Див. Документацію Promise


3
Це не спрацювало б, якщо два окремі (основні) файли викликають цю функцію без готовності foo, так? Буде звільнений лише один із їх зворотних дзвінків, залежно від того, хто з них останній дзвонив.
laggingreflex

У цьому випадку так. Бо ми не управляємо стеком зворотних дзвінків. Але це легко вирішити за допомогою масиву для зберігання всіх зворотних дзвінків.
Технів

Деталі: ReferenceError: async не визначено
1nstinct

1
У мене є 2 запитання: (1) У чому полягає суть блоку else у вашому першому прикладі, де ви говорите if(typeof foo != 'undefined'){ cb(foo); // If foo is already define, I don't wait. } else { callback = cb; }. (2) Чи означає цей блок, що requires цього модуля продовжує викликати його, поки він не дасть значення (з асинхронного шляху)? Або це передбачає, що модулю буде надано лише 1 зворотний виклик протягом усього його життя, тобто наступні виклики можуть пропустити cbаргумент?
Я хочу відповіді

1
@IWantAnswers, у цьому прикладі модулю може знадобитися багаторазовий час для різних модулів, яким потрібно використовувати fooзначення. Але ви не знаєте, коли це сталося. Отже, коли рано, а fooзначення ще не існує, ви зберігаєте зворотні виклики, щоб дочекатися повернення асинхронного виклику. В кінці процесу асинхронізації всі збережені зворотні виклики виймаються, і масив більше не використовується. На даний момент, якщо інший модуль вимагає цього модуля і підписався на отримання fooзначення, значення вже встановлено, тому ви обходите магазин, щоб безпосередньо виконати зворотний виклик.
Технів

16

Підхід ES7 був би негайно викликаною асинхронною функцією в 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
  };
})()

Це може знадобитися з await пізніше:

(async function(){

  var foo = await require("./theuppercode");
  console.log(foo);
})();

Чи можете ви пояснити різницю / наслідки між цим посиланням і ні?
Бернардо Дал Корно,

1
Якщо ви не викликаєте функцію, ви експортуєте функцію, не виконуючи її.
Jonas Wilms

14

Відповідь ES6, використовуючи обіцянки:

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) })

Або ви можете повернути безпосередньо Обіцянку:

const p = new Promise(...)
export default p
...
import p from './asyncModule'
p.then(...)

1
Це правильна, сучасна відповідь на ES6 та Promises. Дякую за це.
Джошуа Пінтер

1
Питання: Чи є причина, чому ви повертаєте функцію замість Promiseбезпосередньо? Якщо ви повернули Promiseбезпосередньо, з якого ви могли б отримати до нього доступ asyncFunc.then(...), так? Досить новий, тому хочу отримати вашу думку.
Джошуа Пінтер,

1
Це теж спрацювало б. Я думаю, коли я писав цей приклад, я експортував клас за допомогою методу async, який сформулював його як функцію. Але ви можете просто експортувати Promise так: const p = new Promise(...); export default p;а потім у своєму модулі імпортуimport p from '...'; p.then(...);
inostia

Чудово, дякую за роз’яснення. Я припускаю, що це особисті уподобання чи існує найкращий спосіб використовувати той чи інший?
Джошуа Пінтер

Я думаю, це залежить від того, чи потрібно вам передавати аргумент своєму асинхронному модулю, як правило, це стосується мене (наприклад, an idабо інших параметрів). У першому прикладі, якщо const asyncFunc = (id) => ...тоді ви могли б використовувати idу своїй функції. Ви б назвали це як asyncFunc(id).then(...). Але якщо вам не потрібно передавати будь-які аргументи, повернення обіцянки також є нормальним.
inostia

11

Іншим підходом було б загортання змінної всередині об’єкта.

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

Якщо в ініціалізаторі є помилка, принаймні ви все одно отримуєте неініціалізоване значення замість зависання зворотного виклику.


3
Як отримати "foo", коли вам потрібен модуль?
HelpMeStackOverflowMyOnlyHope

1
var wrapper = require ('обгортка'); console.log (
wrapper.foo

9

Ви також можете скористатися обіцянками:

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

Те ж саме може трапитися в іншому модулі і також вирішить, як очікувалося:

in-some-other-module.js

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

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

Зверніть увагу, що об’єкт обіцянки створюється один раз, а потім кешується вузлом. Кожен require('./some-async-module')поверне той самий екземпляр об’єкта (у цьому випадку екземпляр обіцянки).


0

Інші відповіді, здавалося, були частковими і не працювали для мене. Це здається дещо повним:

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
});

Чому у вас callback(null, wrapper.foo);замість callback(wrapper.foo);?
Я хочу відповіді

@IWantAnswers Перший аргумент - помилка, а другий - результат
tsuz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.