0

In this question : How to add a custom property or method to a promise? there are simple solutions regarding how to "add properties" to a promise function when the properties are known in advance.

For the clientside-require module I am attempting to enable packages that the require() function loads to append their own properties to the promise that require() returns. For example, to enable this:

var promise_view_loader = require("clientside-view-loader")
promise_view_loader_package
    .load("clientside-view-modal-login_signup")
    .generate()
    .then((modal)=>{
        document.body.appendChild(modal);
        modal.show("login");
    })

or this

var promies_request_package = require("clientside-request")
promies_request_package
    .request("https://google.com")
    .then((response)=>{
       console.log(response)
    })

The problem is that each of these packages we are requiring should be able to define their own custom properties. In other words, we do not know the properties synchronously. First we need to resolve promise_module_properties and then based on those properties the properties of the promise produced by require must be modified.

Is this possible?

Ulad Kasach
  • 11,558
  • 11
  • 61
  • 87
  • No. The promise returns immeadiately, long before the connection to the server is etablished. So how ahould it know the methods? – Jonas Wilms Feb 15 '18 at 16:51
  • @JonasW. I think I have a solution actually... working on it now. – Ulad Kasach Feb 15 '18 at 16:56
  • 1
    I guess your best bet would be a proxy. However, making an asynchronous promise chain look like a synchronous method chain will definitely piss many people off. – Bergi Feb 15 '18 at 17:14
  • @Bergi Proxy is what I was thinking too. Why do you think it would piss people off? – Ulad Kasach Feb 15 '18 at 17:25
  • 2
    Because you cannot see that it is a promise. Because you cannot see when its side effects are happening. Because it's unclear where the chain ends. Because you cannot see which individual parts are asynchronous, and which parts are doing synchronous things (and could be put in the same `then` callback together) but are on a promise chain. In general, it's better to be explicit than implicit. That the language allows us to do crazy things doesn't mean we should do them. – Bergi Feb 15 '18 at 17:31
  • 1
    For example `typeof require("events").EventEmitter`. Or `if (require("process").version > "1.3")`. This will throw people off - *even* when they know that your `require` is asynchronous. – Bergi Feb 15 '18 at 17:34

1 Answers1

0

As Bergi noted, just because we can do this does not mean we should. In fact, i highly recommend against it.

Regardless, it is possible using a builder and a proxy:

Assume the properties are asynchronously defined as follows:

var properties_to_append = {
    load : function(path){
        return this.then((view_loader)=>{ console.log("loading " + path); return view_loader.load(path)}) // define `view_loader.load()` to the view_loader promise
    },
    generate : function(options){
        return this.then((compiler)=>{ return compiler.generate(options) })
    },
}
var promise_properties = Promise.resolve(properties_to_append);

Then utilizing the AsyncPropertyPromise class defined further down the following works as expected:

var async_property_promise = new AsyncPropertyPromise(require("clientside-view-loader"), promise_properties);
async_property_promise // works
    .load("clientside-view-modal-login_signup") // works
    .generate() // works
    .then((modal)=>{
        document.body.appendChild(modal);
        modal.show("login");
    })

AsyncPropertyPromise:

var unknown_properties_deferment_handler = {
    return_defined_target_value : function(target, prop){
        var value = target[prop];
        var bound_value = typeof value == 'function' ? value.bind(target) : value; // bind functions to target, as they would expect
        return bound_value; // return the requested name or parameters
    },
    get: function(target, prop) {
        if(prop in target){
            return this.return_defined_target_value(target, prop); // if the requested method or parameter is in the target object, just return it
        } else {
            return target.promise_to_attempt_to_get_async_property(prop);
        }
    }
};

class AsyncPropertyPromise {
    constructor(original_promise, promise_properties) {
        this.original_promise = original_promise;
        this.promise_properties = promise_properties;
        var proxied_self = new Proxy(this, unknown_properties_deferment_handler);
        return proxied_self;
    }
    then(...args) {
        return this.original_promise.then(...args);
    }
    catch(...args){
        return this.original_promise.catch(...args);
    }
    promise_to_attempt_to_get_async_property(property){
        /*
            1. return a function - NOTE - this assumes that any property not statically defiend is a function
            2. make that function resolve with an AsnycPropertyPromise that
                a. returns the value of the property (method) if it exists
                b. throws error if it does not
        */
        return function(...args){ // 1
            var raw_response_promise = this.promise_properties // 2
                .then((loaded_properties)=>{
                    if(!(property in loaded_properties)) throw "property not defined"; // 2.a
                    var value = loaded_properties[property];
                    var bound_value = value.bind(this); // bind to original_promise
                    return bound_value(...args); // evaluate and return response while passing orig arguments; see `spread` https://stackoverflow.com/a/31035825/3068233
                });
            var async_proxied_response_promise = this._wrap_a_promise(raw_response_promise);
            return async_proxied_response_promise;
        }
    }
    _wrap_a_promise(raw_promise){
        return new this.constructor(raw_promise, this.promise_properties);
    }
}
Ulad Kasach
  • 11,558
  • 11
  • 61
  • 87