Асинхронная загрузка свойства модуля
Я определил модуль (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; }
});
});
}
}
}
Вы также можете использовать обещания обещания вместо простого обратного вызова.