Fixed promises and its race conditions

This commit is contained in:
Marcin Kurczewski 2014-10-02 00:30:25 +02:00
parent e1ae4eaa0d
commit 455ae2b881
18 changed files with 113 additions and 59 deletions

View file

@ -86,7 +86,9 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
jQuery.removeCookie('auth'); jQuery.removeCookie('auth');
appState.set('loginToken', null); appState.set('loginToken', null);
return loginAnonymous().then(resolve).fail(reject); return promise.wait(loginAnonymous())
.then(resolve)
.fail(reject);
}); });
} }

View file

@ -73,7 +73,9 @@ App.BrowsingSettings = function(
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
saveToLocalStorage(); saveToLocalStorage();
if (auth.isLoggedIn()) { if (auth.isLoggedIn()) {
saveToUser(auth.getCurrentUser()).then(resolve).fail(reject); promise.wait(saveToUser(auth.getCurrentUser()))
.then(resolve)
.fail(reject);
} else { } else {
resolve(); resolve();
} }

View file

@ -55,8 +55,7 @@ App.Pager = function(
function retrieve() { function retrieve() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.wait( promise.wait(api.get(url, _.extend({}, searchParams, {page: pageNumber})))
api.get(url, _.extend({}, searchParams, {page: pageNumber})))
.then(function(response) { .then(function(response) {
var pageSize = response.json.pageSize; var pageSize = response.json.pageSize;
var totalRecords = response.json.totalRecords; var totalRecords = response.json.totalRecords;

View file

@ -34,6 +34,9 @@ App.PresenterManager = function(jQuery, topNavigationPresenter, keyboard) {
}, 100); }, 100);
if (lastContentPresenter === null || lastContentPresenter.name !== presenter.name) { if (lastContentPresenter === null || lastContentPresenter.name !== presenter.name) {
if (lastContentPresenter !== null && lastContentPresenter.deinit) {
lastContentPresenter.deinit();
}
keyboard.reset(); keyboard.reset();
topNavigationPresenter.changeTitle(null); topNavigationPresenter.changeTitle(null);
topNavigationPresenter.focus(); topNavigationPresenter.focus();

View file

@ -21,7 +21,7 @@ App.Presenters.HomePresenter = function(
topNavigationPresenter.select('home'); topNavigationPresenter.select('home');
topNavigationPresenter.changeTitle('Home'); topNavigationPresenter.changeTitle('Home');
promise.waitAll( promise.wait(
util.promiseTemplate('home'), util.promiseTemplate('home'),
util.promiseTemplate('post-content'), util.promiseTemplate('post-content'),
api.get('/globals'), api.get('/globals'),

View file

@ -58,7 +58,7 @@ App.Presenters.LoginPresenter = function(
return false; return false;
} }
auth.loginFromCredentials(userNameOrEmail, password, remember) promise.wait(auth.loginFromCredentials(userNameOrEmail, password, remember))
.then(function(response) { .then(function(response) {
finishLogin(); finishLogin();
}).fail(function(response) { }).fail(function(response) {

View file

@ -52,7 +52,7 @@ App.Presenters.PagerPresenter = function(
pager.setSearchParams(args.searchParams); pager.setSearchParams(args.searchParams);
pager.setPage(args.page || 1); pager.setPage(args.page || 1);
retrieve() promise.wait(retrieve())
.then(loaded) .then(loaded)
.fail(loaded); .fail(loaded);
@ -62,6 +62,11 @@ App.Presenters.PagerPresenter = function(
} }
} }
function deinit() {
detachNextPageLoader();
}
function prevPage() { function prevPage() {
pager.prevPage(); pager.prevPage();
syncUrl(); syncUrl();
@ -126,7 +131,7 @@ App.Presenters.PagerPresenter = function(
showSpinner(); showSpinner();
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
pager.retrieve() promise.wait(pager.retrieve())
.then(function(response) { .then(function(response) {
updateCallback(response, forceClear || !endlessScroll); updateCallback(response, forceClear || !endlessScroll);
forceClear = false; forceClear = false;
@ -176,6 +181,10 @@ App.Presenters.PagerPresenter = function(
}, 100); }, 100);
} }
function detachNextPageLoader() {
window.clearInterval(scrollInterval);
}
function showPageList() { function showPageList() {
$pageList.show(); $pageList.show();
} }
@ -224,6 +233,7 @@ App.Presenters.PagerPresenter = function(
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit,
setPage: setPage, setPage: setPage,
setSearchParams: setSearchParams, setSearchParams: setSearchParams,
}; };

View file

@ -25,7 +25,7 @@ App.Presenters.PostListPresenter = function(
topNavigationPresenter.changeTitle('Posts'); topNavigationPresenter.changeTitle('Posts');
searchArgs = util.parseComplexRouteArgs(args.searchArgs); searchArgs = util.parseComplexRouteArgs(args.searchArgs);
promise.waitAll( promise.wait(
util.promiseTemplate('post-list'), util.promiseTemplate('post-list'),
util.promiseTemplate('post-list-item')) util.promiseTemplate('post-list-item'))
.then(function(listHtml, itemHtml) { .then(function(listHtml, itemHtml) {
@ -60,6 +60,10 @@ App.Presenters.PostListPresenter = function(
order: searchArgs.order}}); order: searchArgs.order}});
} }
function deinit() {
pagerPresenter.deinit();
}
function render() { function render() {
$el.html(listTemplate()); $el.html(listTemplate());
$searchInput = $el.find('input[name=query]'); $searchInput = $el.find('input[name=query]');
@ -107,6 +111,7 @@ App.Presenters.PostListPresenter = function(
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit,
render: render, render: render,
}; };

View file

@ -49,7 +49,7 @@ App.Presenters.PostPresenter = function(
editPrivileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail); editPrivileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail);
editPrivileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations); editPrivileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations);
promise.waitAll( promise.wait(
util.promiseTemplate('post'), util.promiseTemplate('post'),
util.promiseTemplate('post-edit'), util.promiseTemplate('post-edit'),
util.promiseTemplate('post-content'), util.promiseTemplate('post-content'),
@ -74,7 +74,7 @@ App.Presenters.PostPresenter = function(
function reinit(args, loaded) { function reinit(args, loaded) {
postNameOrId = args.postNameOrId; postNameOrId = args.postNameOrId;
refreshPost() promise.wait(refreshPost())
.then(function() { .then(function() {
topNavigationPresenter.changeTitle('@' + post.id); topNavigationPresenter.changeTitle('@' + post.id);
render(); render();
@ -84,7 +84,7 @@ App.Presenters.PostPresenter = function(
function refreshPost() { function refreshPost() {
return promise.make(function(resolve, reject) { return promise.make(function(resolve, reject) {
promise.waitAll(api.get('/posts/' + postNameOrId)) promise.wait(api.get('/posts/' + postNameOrId))
.then(function(postResponse) { .then(function(postResponse) {
post = postResponse.json; post = postResponse.json;
postScore = postResponse.json.ownScore; postScore = postResponse.json.ownScore;
@ -170,7 +170,7 @@ App.Presenters.PostPresenter = function(
} }
function deletePost() { function deletePost() {
api.delete('/posts/' + post.id) promise.wait(api.delete('/posts/' + post.id))
.then(function(response) { .then(function(response) {
router.navigate('#/posts'); router.navigate('#/posts');
}).fail(showGenericError); }).fail(showGenericError);
@ -185,11 +185,10 @@ App.Presenters.PostPresenter = function(
} }
function featurePost() { function featurePost() {
api.post('/posts/' + post.id + '/feature') promise.wait(api.post('/posts/' + post.id + '/feature'))
.then(function(response) { .then(function(response) {
router.navigate('#/home'); router.navigate('#/home');
}) }).fail(showGenericError);
.fail(showGenericError);
} }
function editButtonClicked(e) { function editButtonClicked(e) {
@ -304,19 +303,17 @@ App.Presenters.PostPresenter = function(
} }
function addFavorite() { function addFavorite() {
api.post('/posts/' + post.id + '/favorites') promise.wait(api.post('/posts/' + post.id + '/favorites'))
.then(function(response) { .then(function(response) {
refreshPost().then(renderSidebar); promise.wait(refreshPost()).then(renderSidebar);
}) }).fail(showGenericError);
.fail(showGenericError);
} }
function deleteFavorite() { function deleteFavorite() {
api.delete('/posts/' + post.id + '/favorites') promise.wait(api.delete('/posts/' + post.id + '/favorites'))
.then(function(response) { .then(function(response) {
refreshPost().then(renderSidebar); promise.wait(refreshPost()).then(renderSidebar);
}) }).fail(showGenericError);
.fail(showGenericError);
} }
function scoreUpButtonClicked(e) { function scoreUpButtonClicked(e) {
@ -332,11 +329,10 @@ App.Presenters.PostPresenter = function(
} }
function score(scoreValue) { function score(scoreValue) {
api.post('/posts/' + post.id + '/score', {score: scoreValue}) promise.wait(api.post('/posts/' + post.id + '/score', {score: scoreValue}))
.then(function() { .then(function() {
refreshPost().then(renderSidebar); promise.wait(refreshPost()).then(renderSidebar);
}) }).fail(showGenericError);
.fail(showGenericError);
} }
function showEditError(response) { function showEditError(response) {

View file

@ -47,7 +47,7 @@ App.Presenters.RegistrationPresenter = function(
return; return;
} }
api.post('/users', formData) promise.wait(api.post('/users', formData))
.then(function(response) { .then(function(response) {
registrationSuccess(response); registrationSuccess(response);
}).fail(function(response) { }).fail(function(response) {

View file

@ -54,7 +54,7 @@ App.Presenters.UserAccountRemovalPresenter = function(
messagePresenter.showError($messages, 'Must confirm to proceed.'); messagePresenter.showError($messages, 'Must confirm to proceed.');
return; return;
} }
api.delete('/users/' + user.name) promise.wait(api.delete('/users/' + user.name))
.then(function() { .then(function() {
auth.logout(); auth.logout();
var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>'); var $messageDiv = messagePresenter.showInfo($messages, 'Account deleted. <a href="">Back to main page</a>');

View file

@ -120,7 +120,7 @@ App.Presenters.UserAccountSettingsPresenter = function(
delete formData.passwordConfirmation; delete formData.passwordConfirmation;
} }
api.put('/users/' + user.name, formData) promise.wait(api.put('/users/' + user.name, formData))
.then(function(response) { .then(function(response) {
editSuccess(response); editSuccess(response);
}).fail(function(response) { }).fail(function(response) {

View file

@ -70,7 +70,7 @@ App.Presenters.UserActivationPresenter = function(
'/password-reset/' + userNameOrEmail : '/password-reset/' + userNameOrEmail :
'/activation/' + userNameOrEmail; '/activation/' + userNameOrEmail;
api.post(url) promise.wait(api.post(url))
.then(function(response) { .then(function(response) {
var message = operation === 'passwordReset' ? var message = operation === 'passwordReset' ?
'Password reset request sent.' : 'Password reset request sent.' :
@ -92,7 +92,7 @@ App.Presenters.UserActivationPresenter = function(
'/finish-password-reset/' + token : '/finish-password-reset/' + token :
'/finish-activation/' + token; '/finish-activation/' + token;
api.post(url) promise.wait(api.post(url))
.then(function(response) { .then(function(response) {
var message = operation === 'passwordReset' ? var message = operation === 'passwordReset' ?
'Your new password is <strong>' + response.json.newPassword + '</strong>.' : 'Your new password is <strong>' + response.json.newPassword + '</strong>.' :

View file

@ -51,7 +51,7 @@ App.Presenters.UserBrowsingSettingsPresenter = function(
}, },
}; };
browsingSettings.setSettings(newSettings) promise.wait(browsingSettings.setSettings(newSettings))
.then(function() { .then(function() {
messagePresenter.showInfo($messages, 'Browsing settings updated!'); messagePresenter.showInfo($messages, 'Browsing settings updated!');
}); });

View file

@ -18,7 +18,7 @@ App.Presenters.UserListPresenter = function(
topNavigationPresenter.select('users'); topNavigationPresenter.select('users');
topNavigationPresenter.changeTitle('Users'); topNavigationPresenter.changeTitle('Users');
promise.waitAll( promise.wait(
util.promiseTemplate('user-list'), util.promiseTemplate('user-list'),
util.promiseTemplate('user-list-item')) util.promiseTemplate('user-list-item'))
.then(function(listHtml, itemHtml) { .then(function(listHtml, itemHtml) {
@ -55,6 +55,10 @@ App.Presenters.UserListPresenter = function(
order: searchArgs.order}}); order: searchArgs.order}});
} }
function deinit() {
pagerPresenter.deinit();
}
function render() { function render() {
$el.html(listTemplate()); $el.html(listTemplate());
$el.find('.order a').click(orderLinkClicked); $el.find('.order a').click(orderLinkClicked);
@ -93,6 +97,7 @@ App.Presenters.UserListPresenter = function(
return { return {
init: init, init: init,
reinit: reinit, reinit: reinit,
deinit: deinit,
render: render, render: render,
}; };

View file

@ -27,7 +27,7 @@ App.Presenters.UserPresenter = function(
topNavigationPresenter.select(auth.isLoggedIn(userName) ? 'my-account' : 'users'); topNavigationPresenter.select(auth.isLoggedIn(userName) ? 'my-account' : 'users');
topNavigationPresenter.changeTitle(userName); topNavigationPresenter.changeTitle(userName);
promise.waitAll( promise.wait(
util.promiseTemplate('user'), util.promiseTemplate('user'),
api.get('/users/' + userName)) api.get('/users/' + userName))
.then(function( .then(function(

View file

@ -1,28 +1,57 @@
var App = App || {}; var App = App || {};
App.Promise = function(jQuery) { App.Promise = function(_, jQuery) {
function make(callback) var active = [];
{
function make(callback) {
var deferred = jQuery.Deferred(); var deferred = jQuery.Deferred();
callback(deferred.resolve, deferred.reject); var promise = deferred.promise();
return deferred.promise();
callback(function() {
deferred.resolve.apply(deferred, arguments);
active = _.without(active, promise);
}, function() {
deferred.reject.apply(deferred, arguments);
active = _.without(active, promise);
});
promise.then(function() {
if (!_.contains(active, promise)) {
throw new Error('Broken promise');
}
});
active.push(promise);
return promise;
} }
function wait(promise) { function wait() {
return jQuery.when(promise); var promises = arguments;
var deferred = jQuery.Deferred();
return jQuery.when.apply(jQuery, promises)
.then(function() {
return deferred.resolve.apply(deferred, arguments);
}).fail(function() {
return deferred.reject.apply(deferred, arguments);
});
} }
function waitAll() { function abortAll() {
return jQuery.when.apply(jQuery, arguments); active = [];
}
function getActive() {
return active.length;
} }
return { return {
make: make, make: make,
wait: wait, wait: wait,
waitAll: waitAll, getActive: getActive,
abortAll: abortAll,
}; };
}; };
App.DI.registerSingleton('promise', ['jQuery'], App.Promise); App.DI.registerSingleton('promise', ['_', 'jQuery'], App.Promise);

View file

@ -1,6 +1,6 @@
var App = App || {}; var App = App || {};
App.Router = function(pathJs, _, jQuery, util, appState, presenterManager) { App.Router = function(pathJs, _, jQuery, promise, util, appState, presenterManager) {
var root = '#/'; var root = '#/';
var previousLocation = window.location.href; var previousLocation = window.location.href;
@ -65,6 +65,9 @@ App.Router = function(pathJs, _, jQuery, util, appState, presenterManager) {
} }
} }
} }
//abort every operation that can be executed
promise.abortAll();
previousLocation = window.location.href; previousLocation = window.location.href;
var finalParams = _.extend( var finalParams = _.extend(
@ -87,4 +90,4 @@ App.Router = function(pathJs, _, jQuery, util, appState, presenterManager) {
}; };
App.DI.registerSingleton('router', ['pathJs', '_', 'jQuery', 'util', 'appState', 'presenterManager'], App.Router); App.DI.registerSingleton('router', ['pathJs', '_', 'jQuery', 'promise', 'util', 'appState', 'presenterManager'], App.Router);