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 diff --git a/src/classes/Promise.cls b/src/classes/Promise.cls index 5fe7906..3f3b1cc 100644 --- a/src/classes/Promise.cls +++ b/src/classes/Promise.cls @@ -1,183 +1,313 @@ 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) + */ + 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; + } + + /** + * 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); + } + + + // ____ _ _____ _ _ + // | _ \ _ __ ___ _ __ ___ (_)___ ___| ____|_ _____ ___ _ _| |_(_) ___ _ __ + // | |_) | '__/ _ \| '_ ` _ \| / __|/ _ \ _| \ \/ / _ \/ __| | | | __| |/ _ \| '_ \ + // | __/| | | (_) | | | | | | \__ \ __/ |___ > < __/ (__| |_| | |_| | (_) | | | | + // |_| |_| \___/|_| |_| |_|_|___/\___|_____/_/\_\___|\___|\__,_|\__|_|\___/|_| |_| + // + + /** + * 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