From e68003442fd546126a15d1814ac6985ce0f9f97d 2014-11-15 16:48:33
From: Jonathan Frederic <jdfreder@calpoly.edu>
Date: 2014-11-15 16:48:33
Subject: [PATCH] Add Promise support to models.

---

diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js
index 60c0f44..07dc383 100644
--- a/IPython/html/static/widgets/js/manager.js
+++ b/IPython/html/static/widgets/js/manager.js
@@ -172,11 +172,19 @@ define([
 
     WidgetManager.prototype.get_model = function (model_id) {
         // Look-up a model instance by its id.
-        var model = this._models[model_id];
-        if (model !== undefined && model.id == model_id) {
-            return model;
+        var that = this;
+        var model = that._models[model_id];
+        if (model !== undefined) {
+            return new Promise(function(resolve, reject){
+                if (model instanceof Promise) {
+                    model.then(resolve, reject);
+                } else {
+                    resolve(model);
+                }
+            });
+        } else {
+            return undefined;
         }
-        return null;
     };
 
     WidgetManager.prototype._handle_comm_open = function (comm, msg) {
@@ -213,30 +221,32 @@ define([
         //      widget_class: (optional) string
         //          Target name of the widget in the back-end.
         //      comm: (optional) Comm
-        return new Promise(function(resolve, reject) {
+        
+        // Create a comm if it wasn't provided.
+        var comm = options.comm;
+        if (!comm) {
+            comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
+        }
+
+        var that = this;
+        var model_id = comm.comm_id;
+        var promise = new Promise(function(resolve, reject) {
 
             // Get the model type using require or through the registry.
             var widget_type_name = options.model_name;
             var widget_module = options.model_module;
-            var that = this;
             utils.try_load(widget_type_name, widget_module, WidgetManager._model_types)
                 .then(function(ModelType) {
-
-                    // Create a comm if it wasn't provided.
-                    var comm = options.comm;
-                    if (!comm) {
-                        comm = that.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
-                    }
-
-                    var model_id = comm.comm_id;
                     var widget_model = new ModelType(that, model_id, comm);
                     widget_model.on('comm:close', function () {
                       delete that._models[model_id];
                     });
                     that._models[model_id] = widget_model;
-                    reolve(widget_model);
+                    resolve(widget_model);
                 }, reject);
         });
+        this._models[model_id] = promise;
+        return promise;
     };
 
     // Backwards compatibility.
diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js
index 0cebd88..6e99190 100644
--- a/IPython/html/static/widgets/js/widget.js
+++ b/IPython/html/static/widgets/js/widget.js
@@ -92,11 +92,22 @@ define(["widgets/js/manager",
             // Handle when a widget is updated via the python side.
             this.state_lock = state;
             try {
-                var that = this;
-                WidgetModel.__super__.set.apply(this, [Object.keys(state).reduce(function(obj, key) {
-                    obj[key] = that._unpack_models(state[key]);
-                    return obj;
-                }, {})]);
+                var state_keys = [];
+                var state_values = [];
+                for (var state_key in Object.keys(state)) {
+                    if (state.hasOwnProperty(state_key)) {
+                        state_keys.push(state_key);
+                        state_values.push(this._unpack_models(state[state_key]));
+                    }
+                }
+
+                Promise.all(state_values).then(function(promise_values){
+                    var unpacked_state = {};
+                    for (var i = 0; i < state_keys.length; i++) {
+                        unpacked_state[state_keys[i]] = promise_values[i];
+                    }
+                    WidgetModel.__super__.set.apply(this, [unpacked_state]);
+                }, console.error);
             } finally {
                this.state_lock = null;
             }
@@ -254,30 +265,42 @@ define(["widgets/js/manager",
             // Replace model ids with models recursively.
             var that = this;
             var unpacked;
-            if ($.isArray(value)) {
-                unpacked = [];
-                _.each(value, function(sub_value, key) {
-                    unpacked.push(that._unpack_models(sub_value));
-                });
-                return unpacked;
-
-            } else if (value instanceof Object) {
-                unpacked = {};
-                _.each(value, function(sub_value, key) {
-                    unpacked[key] = that._unpack_models(sub_value);
-                });
-                return unpacked;
+            return new Promise(function(resolve, reject) {
 
-            } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
-                var model = this.widget_manager.get_model(value.slice(10, value.length));
-                if (model) {
-                    return model;
+                if ($.isArray(value)) {
+                    unpacked = [];
+                    _.each(value, function(sub_value, key) {
+                        unpacked.push(that._unpack_models(sub_value));
+                    });
+                    Promise.all(unpacked).then(resolve, reject);
+
+                } else if (value instanceof Object) {
+                    unpacked_values = [];
+                    unpacked_keys = [];
+                    _.each(value, function(sub_value, key) {
+                        unpacked_keys.push(key);
+                        unpacked_values.push(that._unpack_models(sub_value));
+                    });
+
+                    Promise.all(unpacked_values).then(function(promise_values) {
+                        unpacked = {};
+                        for (var i = 0; i < unpacked_keys.length; i++) {
+                            unpacked[unpacked_keys[i]] = promise_values[i];
+                        }
+                        resolve(unpacked);
+                    }, reject);    
+
+                } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
+                    var model = this.widget_manager.get_model(value.slice(10, value.length));
+                    if (model) {
+                        model.then(resolve, reject);
+                    } else {
+                        resolve(value);
+                    }    
                 } else {
-                    return value;
-                }
-            } else {
-                    return value;
-            }
+                    resolve(value);
+                }  
+            });
         },
 
         on_some_change: function(keys, callback, context) {
@@ -322,11 +345,11 @@ define(["widgets/js/manager",
             //
             // -given a model and (optionally) a view name if the view name is 
             // not given, it defaults to the model's default view attribute.
+            var that = this;
             return new Promise(function(resolve, reject) {
                 // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
                 // it would be great to have the widget manager add the cell metadata
                 // to the subview without having to add it here.
-                var that = this;
                 options = $.extend({ parent: this }, options || {});
                 
                 this.model.widget_manager.create_view(child_model, options).then(function(child_view) {