Offer a promise for extension worker init

This commit is contained in:
Christopher Willis-Ford 2017-08-22 14:28:38 -07:00
parent 403adb743c
commit 64bde25d22
2 changed files with 62 additions and 20 deletions

View file

@ -28,16 +28,18 @@ const BlockType = require('./block-type');
* @property {string} color1 - the primary color for this category, in '#rrggbb' format
* @property {string} color2 - the secondary color for this category, in '#rrggbb' format
* @property {string} color3 - the tertiary color for this category, in '#rrggbb' format
* @property {Array.<BlockInfo> block - the blocks in this category
*/
/**
* @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing
* @property {string} extensionURL - the URL of the extension to be loaded by this worker
* @property {Function} resolve - function to call on successful worker startup
* @property {Function} reject - function to call on failed worker startup
*/
class ExtensionManager {
constructor () {
/**
* The list of current active extension workers.
* @type {Array.<ExtensionWorker>}
*/
this.workers = [];
/**
* The ID number to provide to the next extension worker.
* @type {int}
@ -45,10 +47,18 @@ class ExtensionManager {
this.nextExtensionWorker = 0;
/**
* The list of extension URLs which have been requested but not yet loaded in a worker.
* @type {Array}
* FIFO queue of extensions which have been requested but not yet loaded in a worker,
* along with promise resolution functions to call once the worker is ready or failed.
*
* @type {Array.<PendingExtensionWorker>}
*/
this.pendingExtensionURLs = [];
this.pendingExtensions = [];
/**
* Map of worker ID to workers which have been allocated but have not yet finished initialization.
* @type {Array.<PendingExtensionWorker>}
*/
this.pendingWorkers = [];
dispatch.setService('extensions', this).catch(e => {
log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
@ -56,21 +66,29 @@ class ExtensionManager {
}
foo () {
this.loadExtensionURL('extensions/example-extension.js');
return this.loadExtensionURL('extensions/example-extension.js');
}
/**
* Load an extension by URL
* @param {string} extensionURL - the URL for the extension to load
* @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
*/
loadExtensionURL (extensionURL) {
return new Promise((resolve, reject) => {
// If we `require` this at the global level it breaks non-webpack targets, including tests
const ExtensionWorker = require('worker-loader!./extension-worker');
this.pendingExtensionURLs.push(extensionURL);
this.pendingExtensions.push({extensionURL, resolve, reject});
dispatch.addWorker(new ExtensionWorker());
});
}
allocateWorker () {
const id = this.nextExtensionWorker++;
const extFile = this.pendingExtensionURLs.shift();
return [id, extFile];
const workerInfo = this.pendingExtensions.shift();
this.pendingWorkers[id] = workerInfo;
return [id, workerInfo.extensionURL];
}
registerExtensionService (serviceName) {
@ -79,6 +97,16 @@ class ExtensionManager {
});
}
onWorkerInit (id, e) {
const workerInfo = this.pendingWorkers[id];
delete this.pendingWorkers[id];
if (e) {
workerInfo.reject(e);
} else {
workerInfo.resolve(id);
}
}
_registerExtensionInfo (serviceName, extensionInfo) {
extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);
dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {
@ -115,7 +143,7 @@ class ExtensionManager {
result.push(this._prepareBlockInfo(serviceName, blockInfo));
} catch (e) {
// TODO: more meaningful error reporting
log.error(`Skipping malformed block: ${e}`);
log.error(`Skipping malformed block: ${JSON.stringify(e)}`);
}
return result;
}, []);

View file

@ -8,13 +8,23 @@ class ExtensionWorker {
constructor () {
this.nextExtensionId = 0;
this.initialRegistrations = [];
dispatch.waitForConnection.then(() => {
dispatch.call('extensions', 'allocateWorker').then(x => {
const [id, extension] = x;
this.workerId = id;
// TODO: catch and report any exceptions here
try {
importScripts(extension);
const initialRegistrations = this.initialRegistrations;
this.initialRegistrations = null;
Promise.all(initialRegistrations).then(() => dispatch.call('extensions', 'onWorkerInit', id));
} catch (e) {
dispatch.call('extensions', 'onWorkerInit', id, e);
}
});
});
@ -25,8 +35,12 @@ class ExtensionWorker {
const extensionId = this.nextExtensionId++;
this.extensions.push(extensionObject);
const serviceName = `extension.${this.workerId}.${extensionId}`;
return dispatch.setService(serviceName, extensionObject)
const promise = dispatch.setService(serviceName, extensionObject)
.then(() => dispatch.call('extensions', 'registerExtensionService', serviceName));
if (this.initialRegistrations) {
this.initialRegistrations.push(promise);
}
return promise;
}
}