Experimental approach to cancelling unnecessary promises

If a view or other resources are destroyed while a promise is being
resolved, subsequent code can fail. This is generally harmless, but it
results in unnecessary errors being logged to the console.

To address this, promises can use a new function,
Zotero.Promise.check(), to test whether a value is truthy or 0 and
automatically throw a specific error that's ignored by the unhandled
rejection handler if not.

Example usage:

getAsync().tap(() => Zotero.Promise.check(this.win));

If this.win is cleaned up while getAsync() is being resolved, subsequent
lines won't be run, and nothing will be logged to the console.
This commit is contained in:
Dan Stillman 2015-05-23 03:04:32 -04:00
parent 3fc09add3a
commit 6b87c641d9
3 changed files with 44 additions and 10 deletions

View file

@ -142,7 +142,8 @@
Zotero.spawn(function* () {
Zotero.debug('Refreshing attachment box');
yield Zotero.Promise.all([this.item.loadItemData(), this.item.loadNote()]);
yield Zotero.Promise.all([this.item.loadItemData(), this.item.loadNote()])
.tap(() => Zotero.Promise.check(this.item));
var attachmentBox = document.getAnonymousNodes(this)[0];
var title = this._id('title');
@ -254,7 +255,8 @@
// Page count
if (this.displayPages) {
var pages = yield Zotero.Fulltext.getPages(this.item.id);
var pages = yield Zotero.Fulltext.getPages(this.item.id)
.tap(() => Zotero.Promise.check(this.item));
var pages = pages ? pages.total : null;
if (pages) {
this._id("pages-label").value = Zotero.getString('itemFields.pages')
@ -273,7 +275,8 @@
if (this.displayDateModified) {
this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
+ Zotero.getString('punctuation.colon');
var mtime = yield this.item.attachmentModificationTime;
var mtime = yield this.item.attachmentModificationTime
.tap(() => Zotero.Promise.check(this.item));
if (mtime) {
this._id("dateModified").value = new Date(mtime).toLocaleString();
}
@ -292,7 +295,8 @@
// Full-text index information
if (this.displayIndexed) {
yield this.updateItemIndexedState();
yield this.updateItemIndexedState()
.tap(() => Zotero.Promise.check(this.item));
indexStatusRow.hidden = false;
}
else {
@ -471,7 +475,8 @@
var indexStatus = this._id('index-status');
var reindexButton = this._id('reindex');
var status = yield Zotero.Fulltext.getIndexedState(this.item);
var status = yield Zotero.Fulltext.getIndexedState(this.item)
.tap(() => Zotero.Promise.check(this.item));
var str = 'fulltext.indexState.';
switch (status) {
case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
@ -495,7 +500,13 @@
var str = Zotero.getString('pane.items.menu.reindexItem');
reindexButton.setAttribute('tooltiptext', str);
if (this.editable && (yield Zotero.Fulltext.canReindex(this.item))) {
var show = false;
if (this.editable) {
show = yield Zotero.Fulltext.canReindex(this.item)
.tap(() => Zotero.Promise.check(this.item));
}
if (show) {
reindexButton.setAttribute('hidden', false);
}
else {

View file

@ -165,6 +165,25 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
this.initializationPromise = this.initializationDeferred.promise;
this.locked = true;
// Add a function to Zotero.Promise to check whether a value is still defined, and if not
// to throw a specific error that's ignored by the unhandled rejection handler in
// bluebird.js. This allows for easily cancelling promises when they're no longer
// needed, for example after a view is destroyed.
//
// Example usage:
//
// getAsync.tap(() => Zotero.Promise.check(this.win))
//
// If this.win is cleaned up while getAsync() is being resolved, subsequent lines won't
// be run, and nothing will be logged to the console.
this.Promise.check = function (val) {
if (!val && val !== 0) {
let e = new Error;
e.name = "ZoteroPromiseInterrupt";
throw e;
}
};
// Load in the preferences branch for the extension
Zotero.Prefs.init();
Zotero.Debug.init(options && options.forceDebugLog);

View file

@ -91,12 +91,16 @@
Promise = e();
// TEMP: Only turn on if debug logging enabled?
Promise.longStackTraces();
Promise.onPossiblyUnhandledRejection(function(error) {
Promise.onPossiblyUnhandledRejection(function (e, promise) {
if (e.name == 'ZoteroPromiseInterrupt') {
return;
}
// Ignore some errors during tests
if (error.message && error.message.indexOf(' -- ignore') != -1) return;
if (e.message && e.message.indexOf(' -- ignore') != -1) return;
self.debug('Unhandled rejection:\n\n' + error.stack);
throw error;
self.debug('Unhandled rejection:\n\n' + e.stack);
throw e;
});
return;