Асинхронная загрузка свойства модуля

Я определил модуль (module1), который должен загружать значение свойства асинхронно. Как я могу использовать это свойство в моем приложении, как только оно определено и только после того, как оно определено?

Моя настройка (упрощенная)

v1

app.js

require(['module1'], function(mod) {
    document.getElementById("greeting").value = mod.getPersonName();
});

module1.js

define(['jquery'], function($) {
    _person;

    $.get('values/person.json')
        .done(function(data) {
            _person = data
        });

    return {
        getPersonName: function() { return _person.name; }
    }

Значения /person.json

{ name: 'John Doe', age: 34 }    

Это работает только в том случае, если GET происходит почти мгновенно, в противном случае происходит сбой, потому что _person не определен при вызове getPersonName.

v2

Чтобы противостоять этому, я решил зарегистрировать обратный вызов, чтобы уведомить приложение, когда человек был загружен.

app.js

require(['module1'], function(mod) {
    mod.onPersonLoaded(function() {
        document.getElementById("greeting").value = mod.getPersonName();
    });
});

module1.js

define(['jquery'], function($) {
    _person;
    _onLoaded;

    $.get('values/person.json')
        .done(function(data) {
            _person = data;
            _onLoaded();
        });

    return {
        getPersonName: function() { return _person.name; },
        onPersonLoaded: function(cb) { _onLoaded = cb; }
    }
}

Это работает, если GET медленный, однако, если он быстрый, _onLoaded не определен, когда .done() называется.

Есть ли хороший способ использовать значения _person в app.js, как только они определены и только после того, как они определены?

Я использую RequireJS, но мой вопрос в целом применим к AMD.

редактировать

Упрощая мой пример, я удалил слой, который может быть важным. Я использую RactiveJS для пользовательского интерфейса.

Настройка (чуть менее упрощенная)

app.js

require(['Ractive', 'module1'], function(Ractive, mod) {

    var ractive = new Ractive({
        ...
        data : {
            name: mod.getPersonName()
        }
    });

    ractive.observe(...);
    ractive.on(...);

});

Редактировать 2

Мое текущее решение, может быть изменено. Зарегистрируйте обратный вызов, который уведомляет app.js, когда человек загружен. Обратный вызов вызывается немедленно, если человек уже загружен при регистрации обратного вызова.

app.js

require(['Ractive', 'module1'], function(Ractive, mod) {

    var ractive = new Ractive({
        ...
        data : {}
    });

    mod.watchPerson(function() {
        ractive.set('person.name', mod.getPersonName());
    });

    ractive.observe(...);
    ractive.on(...);

});

module1.js

define(['jquery'], function($) {
    _person;
    _onLoaded;

    $.get('values/person.json')
        .done(function(data) {
            _person = data;
            try {
                _onLoaded();
            } catch (e) {
                // that was fast!
                // callback will be called when it is registered
        });

    return {
        getPersonName: function() { return _person.name; },
        watchPerson: function(cb) { 
            _onLoaded = cb;
            if(_person != null) {
                _onLoaded();
            }
        }
    }
}

2 ответа

Обещания - хороший выбор, потому что обратные вызовы всегда вызываются асинхронно - вы никогда не столкнетесь с такой запутанной ситуацией, когда _onLoaded() вызывается до того, как он спроектирован.

К сожалению, реализация обещаний в jQuery не соответствует спецификации Promises/A+, поэтому вы не получаете такую ​​гарантию. Если возможно, вы можете использовать альтернативную библиотеку AJAX, такую ​​как Reqwest, и сделать что-то вроде

app.js

define(['module1'], function (mod) {
  mod.then(function(person) {
    // do something with person.name
  }
});

module1.js

define(['reqwest'], function (reqwest) {
  return reqwest('values/person.json');
});

Использование плагина загрузчика текста

Другой вариант, так как вы уже используете AMD, будет использовать плагин загрузчика, такой как текст:

app.js

define(['module1'], function (person) {
  // do something with person.name
});

module1.js

define(['text!values/person.json'], function (personJSON) {
  return JSON.parse(personJSON);
});

Использование плагина загрузчика текста

На самом деле есть даже плагин JSON, так что вы можете полностью отказаться от module1 в этом примере:

app.js

define(['json!values/person'], function (person) {
  // do something with person.name
});

Вот как бы я это сделал. По сути, он не сильно отличается от вашего V2, но добавляет больше инкапсуляции.

app.js

require(['module1'], function(mod) {
    mod.loadPerson(function(person) {
        document.getElementById("greeting").value = person.getPersonName();
    });
});

module1.js

define(['jquery'], function($) {
    return {
        loadPerson : function(callback) {
            $.get('values/person.json').done(function(data) {
                  _person = data;

                 callback({
                    getPersonName: function() { return _person.name; }
                 });
            });

        }
    }
}

Вы также можете использовать обещания обещания вместо простого обратного вызова.

Другие вопросы по тегам