Browse Source

Merge pull request #3618 from RaananW/notifyWithPromises

Notify with promises
Raanan Weber 7 years ago
parent
commit
6db02e9afa
2 changed files with 63 additions and 2 deletions
  1. 1 0
      dist/preview release/what's new.md
  2. 62 2
      src/Tools/babylon.observable.ts

+ 1 - 0
dist/preview release/what's new.md

@@ -32,6 +32,7 @@
 - VRHelper will notify now onSelectedMeshUnselected observable to subscribers when the applied ray selection predicate does not produce a hit and a mesh compliant with the meshSelectionPredicate was previously selected
    ([carloslanderas](https://github.com/carloslanderas))
 - (Viewer) initScene and initEngine can now be extended. onProgress during model loading is implemented as observable. ([RaananW](https://github.com/RaananW))
+- The observable can now notify observers using promise-based callback chain. ([RaananW](https://github.com/RaananW))
 
 ## Bug fixes
 - Texture extension detection in `Engine.CreateTexture` ([sebavan](https://github.com/sebavan))

+ 62 - 2
src/Tools/babylon.observable.ts

@@ -39,6 +39,12 @@
          * The current object in the bubbling phase
          */
         public currentTarget?: any;
+
+        /**
+         * This will be populated with the return value of the last function that was executed.
+         * If it is the first function in the callback chain it will be the event data.
+         */
+        public lastReturnValue?: any;
     }
 
     /**
@@ -187,13 +193,14 @@
             state.target = target;
             state.currentTarget = currentTarget;
             state.skipNextObservers = false;
+            state.lastReturnValue = eventData;
 
             for (var obs of this._observers) {
                 if (obs.mask & mask) {
                     if (obs.scope) {
-                        obs.callback.apply(obs.scope, [eventData, state])
+                        state.lastReturnValue = obs.callback.apply(obs.scope, [eventData, state])
                     } else {
-                        obs.callback(eventData, state);
+                        state.lastReturnValue = obs.callback(eventData, state);
                     }
                 }
                 if (state.skipNextObservers) {
@@ -204,6 +211,59 @@
         }
 
         /**
+         * Calling this will execute each callback, expecting it to be a promise or return a value.
+         * If at any point in the chain one function fails, the promise will fail and the execution will not continue.
+         * This is useful when a chain of events (sometimes async events) is needed to initialize a certain object
+         * and it is crucial that all callbacks will be executed.
+         * The order of the callbacks is kept, callbacks are not executed parallel.
+         * 
+         * @param eventData The data to be sent to each callback
+         * @param mask is used to filter observers defaults to -1
+         * @param target the callback target (see EventState)
+         * @param currentTarget The current object in the bubbling phase
+         * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
+         */
+        public notifyObserversWithPromise(eventData: T, mask: number = -1, target?: any, currentTarget?: any): Promise<T> {
+
+            // create an empty promise
+            let p: Promise<any> = Promise.resolve(eventData);
+
+            // no observers? return this promise.
+            if (!this._observers.length) {
+                return p;
+            }
+
+            let state = this._eventState;
+            state.mask = mask;
+            state.target = target;
+            state.currentTarget = currentTarget;
+            state.skipNextObservers = false;
+
+            // execute one callback after another (not using Promise.all, the order is important)
+            this._observers.forEach(obs => {
+                if (state.skipNextObservers) {
+                    return;
+                }
+                if (obs.mask & mask) {
+                    if (obs.scope) {
+                        p = p.then((lastReturnedValue) => {
+                            state.lastReturnValue = lastReturnedValue;
+                            return obs.callback.apply(obs.scope, [eventData, state]);
+                        });
+                    } else {
+                        p = p.then((lastReturnedValue) => {
+                            state.lastReturnValue = lastReturnedValue;
+                            return obs.callback(eventData, state);
+                        });
+                    }
+                }
+            });
+
+            // return the eventData
+            return p.then(() => { return eventData; });
+        }
+
+        /**
          * Notify a specific observer
          * @param eventData
          * @param mask