Skip to content

Async functions with variable number of args won't work #180

Open
@aminmarashi

Description

@aminmarashi

Consider the following code example:

function init(interpreter, scope) {
    interpreter.setProperty(scope, 'delayedLog', this.createAsyncFunction(function() {
        var args = [].slice.call(arguments);
        var callback = args.pop();
        setTimeout(function() { callback(console.log.apply(null, args)) }, 1000)
    }));
}

(new Interpreter('delayedLog("hi")', init)).run();

If executed, the above code throws this error:

RangeError: Invalid array length

The reason seems to be this commit which forces the position of callback to be always the same as the function definition.

I can see the point of the above commit, but would be nice if we could somehow find a workaround for this issue as well.

I wrote a workaround for this issue, which is enough for my existing use-case:

var MAX_ARGS = 100;

Interpreter.prototype.createAsyncFunctionVarArgs = function(func) {
    var interpreter = this;
    var asyncWrapper = function asyncWrapper() {
        var args = [].slice.call(arguments);
        var callback = args.pop();
        var reversedArgs = args.slice().reverse();
        // Remove all extra undefined from end of the args list
        var firstDefinedItem = reversedArgs.findIndex(function(i) { return i !== undefined });
        var trimmedArgs = firstDefinedItem < 0
            ? []
            : reversedArgs.slice(firstDefinedItem).reverse();
        var nativeArgs = trimmedArgs.map(function(arg) {
            return interpreter.pseudoToNative(arg);
        }).concat(callback);
        func.apply(null, nativeArgs);
    }
    // All functions will have 100 arguments + 1 callback
    Object.defineProperty(asyncWrapper, 'length', { value: MAX_ARGS + 1 });
    return interpreter.createAsyncFunction(asyncWrapper);
};

function init(interpreter, scope) {
    interpreter.setProperty(scope, 'delayedLog', interpreter.createAsyncFunctionVarArgs(function() {
        var args = [].slice.call(arguments);
        var callback = args.pop();
        setTimeout(function() { callback(console.log.apply(null, args)) }, 1000)
    }));
}

(new Interpreter('delayedLog("hi")', init)).run();

The biggest issue with the above workaround is that it makes every function args size = 101, that is not a very nice and efficient solution.


Instead of the above solution, I would like to propose adding support for async functions that return Promise to the interpreter code, another advantage to this is that the Promise abstraction can also be used to distinguish failure and successful results.

If we use Promise, we don't need to pass the callback and therefore we can leave the arguments unchanged.

I think because the Promise object is created outside of the interpreter code, this means the existing code will still work on older browsers.

I would love to create a PR with whatever solution you think is best.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions