The Waiter Pattern

The Waiter Pattern is a technique that involves monitoring multiple asynchronous tasks and triggering future actions when these tasks are completed. This concept has been present even before the introduction of the Promise model. The difference lies in the fact that it wasn't standardized as a specific technical specification. The Waiter Pattern does not fall within the traditional definition of the 23 design patterns. It is generally regarded as a broad-spectrum, technical design pattern.

Description

The Waiter Pattern involves listening for asynchronous processes to trigger future actions. For instance, in a scenario where operations A and B need to be completed before initiating operation C, this pattern proves useful. In software development, it's quite common to have dependencies on the completion of a prior operation before triggering the next one. In the context of JavaScript being single-threaded and unable to adopt a blocking approach, the conventional method for handling this was by using callbacks before the introduction of the Promise specification. However, this often led to the issue of callback hell. The Waiter Pattern can be seen as a precursor to the Promise specification, serving as a similar solution prior to its standardization.

Implementation

var Waiter = function() {
    var dfd = []; // Placeholder for waiting objects
    var doneArr = []; // Container for success callbacks
    var failArr = []; // Container for failure callbacks

    // Monitoring object class
    var Promise = function() {
        this.resolved = false; // Monitoring the successful resolution status of an object
        this.rejected = false; // Monitoring the failure resolution status of an object
    }

    Promise.prototype = {
        // Successful resolution
        resolve: function() {
            this.resolved = true; // Sets the current monitoring status to successful
            if (!dfd.length) return void 0;
            for (var i = dfd.length - 1; i >= 0; i--) {
                // Iterates through monitoring objects, returning if any of them are not resolved or are rejected
                if (dfd[i] && !dfd[i].resolved || dfd[i].rejected) return void 0;
                dfd.splice(i, 1);
            }
            _exec(doneArr);
        },
        // Failure resolution
        reject: function() {
            this.rejected = true; // Sets the current monitoring status to failed
            if (!dfd.length) return void 0; // No monitoring objects to cancel
            dfd.splice(0); // Clears the monitoring objects
            _exec(failArr);
        }
    }

    this.Deferred = function() {
        return new Promise();
    };

    // Callback execution method
    function _exec(arr) {
        for (let i = 0, len = arr.length; i < len; i++) {
            try {
                arr[i] && arr[i]();
            } catch (e) {
                // console.warn("Error", e);
                _exec(failArr);
            }
        }
    };

    // Monitor asynchronous method parameters
    this.when = function(...args) {
        // Sets the monitoring objects
        dfd = args;
        var i = args.length;
        // Iterates through the monitoring objects in reverse
        for (--i; i >= 0; i--) {
            // If the object does not exist, is already resolved, is rejected, or is not an instance of Promise
            if (!args[i] || args[i].resolved || args[i].rejected || !args[i] instanceof Promise) {
                args.splice(i, 1)
            }
        }
        return this; // Returns the waiter object
    };

    // Adds successful resolution callback functions
    this.done = function(...args) {
        doneArr = doneArr.concat(args); // Adds callback methods to the success callback function container
        return this;
    };


```javascript
// To solve the failure callback function addition method
this.fail = function(...args) {
    failArr = failArr.concat(args); // Add methods to the failure callback function
    return this;
};
}

;(function(){
    var waiter = new Waiter(); // Create a waiter instance
    var first = function() {
        var promise = waiter.Deferred();
        setTimeout(() => {
            promise.resolve();
        }, 1000);
        return promise; // Return the listening object
    }();
    var second = function() { // The second object
        var promise = waiter.Deferred();
        setTimeout(() => {
            promise.resolve();
        }, 2000);
        return promise;
    }();
    waiter.when(first, second).done(() => {
        console.log("success");
    }).fail(() => {
        console.log("fail");
    })
})();

;(function(){
    var waiter = new Waiter(); // Create a waiter instance
    var first = function() {
        var promise = waiter.Deferred();
        setTimeout(() => {
            promise.resolve();
        }, 1000);
        return promise; // Return the listening object
    }();
    var second = function() { // The second object
        var promise = waiter.Deferred();
        setTimeout(() => {
            promise.resolve();
        }, 3000);
        return promise;
    }();
    waiter.when(first, second).done(() => {
        throw new Error("test");
    }).fail(() => {
        console.log("fail");
    })
})();

Promise

Promise is a solution for asynchronous operations, used to represent the final completion or failure of an asynchronous operation and its resulting value. Promise has various open-source implementations, and it is uniformly standardized in ES6, directly supported by browsers. The waiter pattern we implemented above is more similar to Promise.all().

Example

This method returns a new promise object. The promise object will only trigger success when all the promise objects in the iterable parameter object are successful. Once any promise object in the iterable fails, the promise object is triggered for failure immediately. After the new promise object is triggered for success, it will return an array containing the return values of all promises in iterable as the return value of the success callback, in the order consistent with iterable. If the new promise object is triggered for failure, it will use the error information of the first failed promise object in iterable as its failure error information. The Promise.all method is commonly used to handle the status collection of multiple promise objects.

var p1 = new Promise((resolve, reject) => {
  resolve("success1");
})

var p2 = new Promise((resolve, reject) => {
  resolve("success2");
})

var p3 = new Promise((resolve, reject) => {
  reject("fail");
})

Promise.all([p1, p2]).then((result) => {
  console.log(result);     // Success status // ["success1", "success2"]
}).catch((error) => {
  console.log(error);
})

Promise.all([p1,p3,p2]).then((result) => {
  console.log(result);
}).catch((error) => {
  console.log(error);      // Failure status // fail
})

Daily Question

https://github.com/WindrunnerMax/EveryDay

References

https://juejin.cn/post/6844903645855612942 https://segmentfault.com/a/1190000021413444 https://www.cnblogs.com/hsp-blog/p/5889842.html