7

Something that is bugging me and my colleague. Consider the following...

const {map, compose} = require('ramda');

compose(
  console.log,
  map(Math.tan)
)([1,2,3]);

compose(
  console.log,
  map(v=>Promise.resolve(v))
)([4,5,6]);

compose(
  console.log,
  map(Promise.resolve)
)([7,8,9]);

As you would expect, the tan of 1, 2 and 3 are output and so are the promises resolving 3, 4 and 5. But my question is... why does the third break? Why doesn't Promise.resolve behave in the same way as any other function?

[ 1.5574077246549023, -2.185039863261519, -0.1425465430742778 ]
[ Promise { 4 }, Promise { 5 }, Promise { 6 } ]
/home/xxx/node_modules/ramda/src/internal/_map.js:6
    result[idx] = fn(functor[idx]);
                  ^

TypeError: PromiseResolve called on non-object
    at resolve (<anonymous>)
    at _map (/home/xxx/node_modules/ramda/src/internal/_map.js:6:19)
    at map (/home/xxx/node_modules/ramda/src/map.js:57:14)
    at /home/xxx/node_modules/ramda/src/internal/_dispatchable.js:39:15
    at /home/xxx/node_modules/ramda/src/internal/_curry2.js:20:46
    at f1 (/home/xxx/node_modules/ramda/src/internal/_curry1.js:17:17)
    at /home/xxx/node_modules/ramda/src/internal/_pipe.js:3:27
    at /home/xxx/node_modules/ramda/src/internal/_arity.js:5:45
    at Object.<anonymous> (/home/xxx/b.js:20:6)
    at Module._compile (module.js:569:30)
thomas-peter
  • 6,952
  • 6
  • 37
  • 56

3 Answers3

21

Promise.resolve refers to the resolve function without context object.

You want to call it with the proper context object. This can be done

  • by calling it on that context object, as in v => Promise.resolve(v), or
  • by creating a bound version of it, as in Promise.resolve.bind(Promise)

So, this would work:

compose(
  console.log,
  map(Promise.resolve.bind(Promise))
)([7,8,9]);

Remember that Javascript does not have classes. Functions have no owner. Objects can store functions in their properties, but that does not mean the function is owned by that object.

Another way is setting the context object explicitly, with Function#call or Function#apply:

function (v) {
    var resolve = Promise.resolve;
    return resolve.call(Promise, v);
}

Maybe it's best illustrated by focusing on something other than a method:

function Foo() {
    this.bar = {some: "value"};
    this.baz = function () { return this.bar; };
}

var f = new Foo();
var b = f.bar;
var z = f.baz;

here b refers to {some: "value"} without {some: "value"} magically "knowing" that f stores a reference to it. This should be obvious.

The same is true of z. It stores a function without that function "knowing" that f also references it. This should be just as obvious, in theory.

Calling z() will yield different results than calling f.baz(), even though the called function is the same one. Only the context is different.

Tomalak
  • 306,836
  • 62
  • 485
  • 598
  • Thanks very much. It is starting to make more sense but I think this is something I'll be digesting for bit. I've been playing a little bit with creating functions as properties of the function object. Trying to understand how resolve() can be called in the first place. – thomas-peter Jul 20 '17 at 19:18
2

When a function is called its this variable is dynamically allocated a value.

The resolve function cares what that value is.

The third part of your code passes the resolve function and then calls it without the context of the Promise object.

This means that this does not get allocated the value of Promise which the function needs.

Quentin
  • 800,325
  • 104
  • 1,079
  • 1,205
  • Thank you Quentin for your reply. It was really helpful. I noticed than typeof Math is an object where as typeof Promise is a function (constructor) which has helped me to try and understand. – thomas-peter Jul 20 '17 at 19:19
1

Promise.resolve needs to be called with this being a Promise constructor (or subclass).

resolve = Promise.resolve;
resolve(null); // Error
resolve.call({}); // Error: Object is not a constructor

So change this line:

map(Promise.resolve)

to:

map(Promise.resolve.bind(Promise))
Ginden
  • 4,383
  • 28
  • 62
  • Thanks Ginden. It's really interesting that Promise.resolve.bind(Promise). That is going to keep my head spinning for a while. – thomas-peter Jul 20 '17 at 19:20