From 68a3e46b4483761f0da3282b8eef4c3bef2b2328 Mon Sep 17 00:00:00 2001 From: Matthew Keasling Date: Fri, 2 Feb 2018 14:22:32 -0800 Subject: [PATCH 1/3] Enhancements Automatically gives Deferred classes the ability to reference the Promise Promise class collects continuous state New Deferred classes can be added to the promise stack at any time Execution can be paused & resumed, which allows for things like Batchable or Schedulable classes to be run within a Promise chain. --- src/classes/Promise.cls | 441 ++++++++++++++++++++++++---------------- 1 file changed, 262 insertions(+), 179 deletions(-) diff --git a/src/classes/Promise.cls b/src/classes/Promise.cls index 5fe7906..1f387f4 100644 --- a/src/classes/Promise.cls +++ b/src/classes/Promise.cls @@ -1,183 +1,266 @@ Public Virtual Class Promise Implements Queueable, Database.AllowsCallouts { - // ___ _ __ __ - // |_ _|_ __ ___| |_ __ _ _ __ ___ ___ \ / /_ _ _ __ ___ - // | || '_ \/ __| __/ _` | '_ \ / __/ _ \ \ / / _` | '__/ __| - // | || | | \__ \ |_ (_| | | | | (__ __/\ V / (_| | | \__ \ - // |___|_| |_|___/\__\__,_|_| |_|\___\___| \_/ \__,_|_| |___/ - // - - /* - * promiseStack is the fundamental data structure used to chain Promise.derer - * instances. Because lists are ordered, we can ensure the order of execution - * of the various steps in this promise chain. - */ - Protected List promiseStack = new List(); - - /** - * promiseData stores the results of the immediately previous execution step - * the .execute(QueueableContext qc) method passes the current value of - * this variable into the *next* Promise.Deferred implementing classes - * resolve method. - * - * N.B. The original call to .execute(Object o) sets the value of this - * this variable to o. - */ - Protected Object promiseData; - - - /** - * These two variables hold references to this promise chain's - * error and done handlers. These handlers are executed in - * the event of an error, or when the chain completes all - * the promiseStacks instances' .resolve() methods - */ - Protected Promise.Error errorHandler; - Protected Promise.Done doneHandler; - - // ____ _ _ - // / ___|___ _ __ ___| |_ _ __ _ _ ___| |_ ___ _ __ ___ - // | | / _ \| '_ \/ __| __| '__| | | |/ __| __/ _ \| '__/ __| - // | |___ (_) | | | \__ \ |_| | | |_| | (__| |_ (_) | | \__ \ - // \____\___/|_| |_|___/\__|_| \__,_|\___|\__\___/|_| |___/ - // - - /* - * Constructor. - * @param Promise.Deferred deferred - Instance of class that implements Promise.Deferred - * returns Promise instance - */ - Public Promise(Promise.Deferred deferred) { - then(deferred); - } - - // __ __ _ _ _ - // | \/ | ___| |_| |__ ___ __| |___ - // | |\/| |/ _ \ __| '_ \ / _ \ / _` / __| - // | | | | __/ |_| | | | (_) | (_| \__ \ - // |_| |_|\___|\__|_| |_|\___/ \__,_|___/ - // - - /** - * Add a new Promise.Deferred class instance to the promise stack - * @param Promise.Deferred deferred class to execute asynchronusly (But in order) - * @return this (for chaining) - */ - Public Promise then(Promise.Deferred deferred) { - promiseStack.add(deferred); - return this; - } - - /** - * Sets the error (Catch) handler. - * While you can only set one error handler, that error handler - * can be written to parse different types etc. - * @param errorHandler The handler to use - * @return this (for chaining) - */ - Public Promise error(Promise.Error errorHandler) { - this.errorHandler = errorHandler; - return this; - } - - /** - * Sets the Done (Finally) handler. - * While you can set only one done handler, you should be aware - * that the done handler *always* runs. *always* - * @param doneHandler The handler to use - * @return this (for chaining) - */ - Public Promise done(Promise.Done doneHandler) { - this.doneHandler = doneHandler; - return this; - } - - - // ____ _ _____ _ _ - // | _ \ _ __ ___ _ __ ___ (_)___ ___| ____|_ _____ ___ _ _| |_(_) ___ _ __ - // | |_) | '__/ _ \| '_ ` _ \| / __|/ _ \ _| \ \/ / _ \/ __| | | | __| |/ _ \| '_ \ - // | __/| | | (_) | | | | | | \__ \ __/ |___ > < __/ (__| |_| | |_| | (_) | | | | - // |_| |_| \___/|_| |_| |_|_|___/\___|_____/_/\_\___|\___|\__,_|\__|_|\___/|_| |_| - // - - /** - * This version of execute kicks off a promise chain. - * @param input Object to pass to the first Promise.deferred - * implementing class in the promiseStack - */ - Public Void execute(Object input) { - promiseData = input; - System.enqueueJob(this); - } - - /** - * This version of execute kicks off a promise chain - * but crucially does not pass any initial data - * to the first promise.deferred object. - */ - Public Void execute() { - System.enqueueJob(this); - } - - /** - * Iterates through the promiseStack instance variable, - * executing each promiseBase.Deferred instance in a Queueable context - * @param context System Injected - * @return Void will either return nothing (void) or enqueue the next - * Next item in the promiseStack - */ - Public Void execute(QueueableContext context) { - try { - Promise.Deferred currentPromise = promiseStack.remove(0); - promiseData = currentPromise.resolve(promiseData); - if (promiseStack.size() > 0) { + // ___ _ __ __ + // |_ _|_ __ ___| |_ __ _ _ __ ___ ___ \ / /_ _ _ __ ___ + // | || '_ \/ __| __/ _` | '_ \ / __/ _ \ \ / / _` | '__/ __| + // | || | | \__ \ |_ (_| | | | | (__ __/\ V / (_| | | \__ \ + // |___|_| |_|___/\__\__,_|_| |_|\___\___| \_/ \__,_|_| |___/ + // + + /** + * promiseStack is the fundamental data structure used to chain Promise.Deferred + * instances. Because lists are ordered, we can ensure the order of execution + * of the various steps in this promise chain. + */ + Protected List promiseStack = new List(); + + /** + * If this is true, the next Deferred in the promiseStack will NOT be run after + * a resolve() method is completed. This allows for other processes (like batch jobs) + * to be inserted, and control the pause/resume flow. + */ + Protected Boolean isPaused = false; + + /** + * promiseData stores the results of the immediately previous execution step + * the .execute(QueueableContext qc) method passes the current value of + * this variable into the *next* Promise.Deferred implementing classes + * resolve method. + * + * N.B. The original call to .execute(Object o) sets the value of this + * this variable to o. + */ + Protected Object promiseData; + + /** + * state is the single place that all Deferreds can use to publish + * data for use by subsequent steps. This could be accomplished by + * passing things through input, but this simplifies it by providing + * consistent access. + */ + Public Map state = new Map(); + + /** + * These two variables hold references to this promise chain's + * error and done handlers. These handlers are executed in + * the event of an error, or when the chain completes all + * the promiseStacks instances' .resolve() methods + */ + Protected Promise.Error errorHandler; + Protected Promise.Done doneHandler; + + // ____ _ _ + // / ___|___ _ __ ___| |_ _ __ _ _ ___| |_ ___ _ __ ___ + // | | / _ \| '_ \/ __| __| '__| | | |/ __| __/ _ \| '__/ __| + // | |___ (_) | | | \__ \ |_| | | |_| | (__| |_ (_) | | \__ \ + // \____\___/|_| |_|___/\__|_| \__,_|\___|\__\___/|_| |___/ + // + + /** + * Constructor. + * returns Promise instance + */ + Public Promise() { + } + + /** + * Constructor. + * @param Promise.Deferred deferred - Instance of class that implements Promise.Deferred + * returns Promise instance + */ + Public Promise(Promise.Deferred deferred) { + then(deferred); + } + + // __ __ _ _ _ + // | \/ | ___| |_| |__ ___ __| |___ + // | |\/| |/ _ \ __| '_ \ / _ \ / _` / __| + // | | | | __/ |_| | | | (_) | (_| \__ \ + // |_| |_|\___|\__|_| |_|\___/ \__,_|___/ + // + + /** + * Add a new Promise.Deferred class instance to the promise stack + * @param Promise.Deferred deferred class to execute asynchronusly (But in order) + * @return this (for chaining) + */ + Public Promise then(Promise.Deferred deferred) { + deferred.setPromise(this); + promiseStack.add(deferred); + return this; + } + + /** + * Add a new Promise.Deferred class instance to the beginning of the promise stack + * @param Promise.Deferred deferred class to execute asynchronously (But in order) + * @return this (for chaining) + */ + Public Promise first(Promise.Deferred deferred) { + deferred.setPromise(this); + promiseStack.add(0, deferred); + return this; + } + + /** + * Sets the error (Catch) handler. + * While you can only set one error handler, that error handler + * can be written to parse different types etc. + * @param errorHandler The handler to use + * @return this (for chaining) + */ + //noinspection ApexIllegalAssignment,ApexUnresolvableReference + Public Promise error(Promise.Error errorHandler) { + errorHandler.setPromise(this); + this.errorHandler = errorHandler; + return this; + } + + /** + * Sets the Done (Finally) handler. + * While you can set only one done handler, you should be aware + * that the done handler *always* runs. *always* + * @param doneHandler The handler to use + * @return this (for chaining) + */ + Public Promise done(Promise.Done doneHandler) { + doneHandler.setPromise(this); + this.doneHandler = doneHandler; + return this; + } + + + // ____ _ _____ _ _ + // | _ \ _ __ ___ _ __ ___ (_)___ ___| ____|_ _____ ___ _ _| |_(_) ___ _ __ + // | |_) | '__/ _ \| '_ ` _ \| / __|/ _ \ _| \ \/ / _ \/ __| | | | __| |/ _ \| '_ \ + // | __/| | | (_) | | | | | | \__ \ __/ |___ > < __/ (__| |_| | |_| | (_) | | | | + // |_| |_| \___/|_| |_| |_|_|___/\___|_____/_/\_\___|\___|\__,_|\__|_|\___/|_| |_| + // + + /** + * This version of execute kicks off a promise chain. + * @param input Object to pass to the first Promise.deferred + * implementing class in the promiseStack + */ + Public Promise execute(Object input) { + if(input instanceof QueueableContext) { + QueueableExecute((QueueableContext) input); + return null; + } else { + promiseData = input; + System.enqueueJob(this); + return this; + } + } + + /** + * This version of execute kicks off a promise chain + * but crucially does not pass any initial data + * to the first promise.deferred object. + */ + Public Promise execute() { System.enqueueJob(this); - return; - } - } catch (Exception e) { - promiseData = errorHandler.error(e); - } - doneHandler.done(promiseData); - } - - // ___ _ __ - // |_ _|_ __ | |_ ___ _ __ / _| __ _ ___ ___ ___ - // | || '_ \| __/ _ \ '__| |_ / _` |/ __/ _ \ __| - // | || | | | |_ __/ | | _| (_| | (__ __\__ \ - // |___|_| |_|\__\___|_| |_| \__,_|\___\___|___/ - // - - /** - * The Deferred interface specifies only the resolve method - * This resolve method must accept and return an Object. - * The Promise.execute() method injects the output of the - * previous step into the current step's resolve method. - * - * This allows you to pass data from one Promise.Deferred - * implementing class to the next. - * - */ - Public Interface Deferred { - Object resolve(Object input); - } - - /** - * The Error interface specifies only the error(Exception e) - * method. It's clunky, but the error method must also - * return an object so that the Done handler can be - * executed after an error occurs. - */ - Public Interface Error { - Object error(Exception e); - } - - /** - * The Done interface requires only the done(Object) method - * to be specified by the end-developer. This method is run - * regardless of error status if it's included in the promise - * chain. - */ - Public Interface Done { - Void done(Object input); - } + return this; + } + + /** + * Iterates through the promiseStack instance variable, + * executing each promiseBase.Deferred instance in a Queueable context + * @param context System Injected + * @return Void will either return nothing (void) or enqueue the next + * Next item in the promiseStack + */ + Public Void Execute(QueueableContext context) { + QueueableExecute(context); + } + Public Void QueueableExecute(QueueableContext context) { + try { + Promise.Deferred currentPromise = promiseStack.remove(0); + promiseData = currentPromise.resolve(promiseData); + if (isPaused) return; + if (promiseStack.size() > 0) { + System.enqueueJob(this); + return; + } + } catch (Exception e) { + promiseData = errorHandler.error(e); + } + doneHandler.done(promiseData); + } + + /** + * Pauses execution until the resume() method is called. This allows + * for interrupted flow, like in Batch or Scheduled jobs. + */ + Public Void pause() { + this.isPaused = true; + } + + /** + * Resumes paused execution. + */ + Public Void resume() { + this.isPaused = false; + if(promiseStack.size() > 0){ + System.enqueueJob(this); + return; + } + doneHandler.done(promiseData); + } + + // ___ _ __ + // |_ _|_ __ | |_ ___ _ __ / _| __ _ ___ ___ ___ + // | || '_ \| __/ _ \ '__| |_ / _` |/ __/ _ \ __| + // | || | | | |_ __/ | | _| (_| | (__ __\__ \ + // |___|_| |_|\__\___|_| |_| \__,_|\___\___|___/ + // + + /** + * The Deferred interface specifies only the resolve method + * This resolve method must accept and return an Object. + * The Promise.execute() method injects the output of the + * previous step into the current step's resolve method. + * + * This allows you to pass data from one Promise.Deferred + * implementing class to the next. + * + */ + Public Abstract Class Deferred Extends Step { + Abstract Object resolve(Object input); + } + + /** + * The Step class provides the setPromise method, which is called + * in then(), error(), and done(). It provides the Promise context + * to the instance, so that it may view and manipulate the state + * and the call stack. + */ + Public Virtual Class Step { + Protected Promise p; + /** + * @param p The Promise to cache + */ + Public Void setPromise(Promise p) { + this.p = p; + } + } + + /** + * The Error interface specifies only the error(Exception e) + * method. It's clunky, but the error method must also + * return an object so that the Done handler can be + * executed after an error occurs. + */ + Public Abstract Class Error Extends Step { + Abstract Object error(Exception e); + } + + /** + * The Done interface requires only the done(Object) method + * to be specified by the end-developer. This method is run + * regardless of error status if it's included in the promise + * chain. + */ + Public Abstract Class Done Extends Step { + Abstract Void done(Object input); + } } \ No newline at end of file From 9f02238a4d4f35ae11eb6c0d01281dbbb5943e73 Mon Sep 17 00:00:00 2001 From: Matthew Keasling Date: Fri, 2 Feb 2018 17:03:50 -0800 Subject: [PATCH 2/3] Ignoring .idea --- .gitignore | 1 + .idea/copyright/Personal.xml | 6 ------ .idea/inspectionProfiles/Project_Default.xml | 12 ------------ 3 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 .idea/copyright/Personal.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.gitignore b/.gitignore index 827e199..96bf052 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea /.idea/encodings.xml /IlluminatedCloud/kjpPromise/OfflineSymbolTable.zip /.idea/modules.xml diff --git a/.idea/copyright/Personal.xml b/.idea/copyright/Personal.xml deleted file mode 100644 index 9d611d7..0000000 --- a/.idea/copyright/Personal.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index e522673..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file From 68df2a0f31e3e48b15fb94c403a118ab3da8b035 Mon Sep 17 00:00:00 2001 From: Matthew Keasling Date: Fri, 2 Feb 2018 17:04:21 -0800 Subject: [PATCH 3/3] Adding state shortcut methods; updating "Interfaces" to "Base Classes" --- src/classes/Promise.cls | 59 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/classes/Promise.cls b/src/classes/Promise.cls index 1f387f4..3f3b1cc 100644 --- a/src/classes/Promise.cls +++ b/src/classes/Promise.cls @@ -1,4 +1,5 @@ Public Virtual Class Promise Implements Queueable, Database.AllowsCallouts { + // ___ _ __ __ // |_ _|_ __ ___| |_ __ _ _ __ ___ ___ \ / /_ _ _ __ ___ // | || '_ \/ __| __/ _` | '_ \ / __/ _ \ \ / / _` | '__/ __| @@ -107,7 +108,6 @@ Public Virtual Class Promise Implements Queueable, Database.AllowsCallouts { * @param errorHandler The handler to use * @return this (for chaining) */ - //noinspection ApexIllegalAssignment,ApexUnresolvableReference Public Promise error(Promise.Error errorHandler) { errorHandler.setPromise(this); this.errorHandler = errorHandler; @@ -127,6 +127,53 @@ Public Virtual Class Promise Implements Queueable, Database.AllowsCallouts { return this; } + /** + * Puts a value into the state. + * + * @param name the String key. + * @param value the Object value. + * + * @return this (for chaining) + */ + Public Promise set(String name, Object value) { + return put(name, value); + } + + /** + * Puts a value into the state. + * + * @param name the String key. + * @param value the Object value. + * + * @return this (for chaining) + */ + Public Promise put(String name, Object value) { + this.state.put(name, value); + return this; + } + + /** + * Returns a value from the state. + * + * @param name the String key. + * + * @return the stored value, or null if not available. + */ + Public Object get(String name) { + return this.state.get(name); + } + + /** + * Tells whether the state contains a key of the given name. + * + * @param name the String key. + * + * @return True if the key exists in state; false otherwise. + */ + Public Boolean containsKey(String name) { + return this.state.containsKey(name); + } + // ____ _ _____ _ _ // | _ \ _ __ ___ _ __ ___ (_)___ ___| ____|_ _____ ___ _ _| |_(_) ___ _ __ @@ -206,11 +253,11 @@ Public Virtual Class Promise Implements Queueable, Database.AllowsCallouts { doneHandler.done(promiseData); } - // ___ _ __ - // |_ _|_ __ | |_ ___ _ __ / _| __ _ ___ ___ ___ - // | || '_ \| __/ _ \ '__| |_ / _` |/ __/ _ \ __| - // | || | | | |_ __/ | | _| (_| | (__ __\__ \ - // |___|_| |_|\__\___|_| |_| \__,_|\___\___|___/ + // ____ ____ _ + // | __ ) __ _ ___ ___ / ___| | __ _ ___ ___ ___ ___ + // | _ \ / _` / __|/ _ \ | | |/ _` / __/ __|/ _ \/ __| + // | |_) | (_| \__ \ __/ |___| | (_| \__ \__ \ __/\__ \ + // |____/ \__,_|___/\___|\____|_|\__,_|___/___/\___||___/ // /**