commit 6b87c641d96c2aabe8f80e26019174db49207fc5
parent 3fc09add3a89d5db70547d942214b3ff6e35b755
Author: Dan Stillman <dstillman@zotero.org>
Date: Sat, 23 May 2015 03:04:32 -0400
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.
Diffstat:
3 files changed, 44 insertions(+), 10 deletions(-)
diff --git a/chrome/content/zotero/bindings/attachmentbox.xml b/chrome/content/zotero/bindings/attachmentbox.xml
@@ -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 {
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
@@ -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);
diff --git a/resource/bluebird.js b/resource/bluebird.js
@@ -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;