Cool Blue Cool Blue - 2 months ago 5
Javascript Question

Re-binding the value returned by resolve in javascript promises

I'm trying to pass context along a promise chain. I thought I could use

function.prototype.bind
since I can see that
Promise.resolve
inherits from
function
but it doesn't work.

In node I get
[TypeError: Promise.resolve.bind(...) is not a function]

In chrome, for example, I get
VM1043:1 Uncaught TypeError: object is not a constructor(…)


Is it possible to pass a base object along the promise chain via context?

var p2 = new Promise(function(resolve, reject) {
var o = {};
o.p1 = 't' + error_or_n('t', '1');
resolve(o)
})
.then(o => {
console.log(o.p1);
return pp(o.p1, '-> q' + error_or_n('t', '2'))
.then( function (_) {
Promise.resolve.bind(o)(o.p1 = _ )
});
})
.then(() => {
var o = this;
console.log(o.p1);
return pp(o.p1, '-> q' + error_or_n('t', '3'))
.then( function (_) {Promise.resolve.bind(o)(o.p1 = _ + '|')});
})
.then(() => {
console.log(this.p1);
})
.catch(e => console.log(e));


EDIT



To restate my objective, I want all promises in a chain to have access to a common base object, without resorting to globals.

My idea was to create a promise chain where all resolutions are rebound to said common base object. It's analogous to
return this
in chainable objects.

The answer below explains why I can't use
bind
to do this and suggests using a closure. The other way I found was to insert an extra
then
, before returning, that carried out some transformation and then returned the base object. This creates the
return this
style chaining that I imagined.

Both options are here

/**
* Created by cool.blue on 11-Sep-16.
*/
var throw_dont = '0'; // enter 1..3 to throw
var error_or_n = (x, y) => {
if(throw_dont && y == throw_dont)
throw(new Error('in ' + x + y));
else
return y
};

/**
* arbitrary function that returns a promise
* */
var pp = (o, n) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 0, `${o.p1}-> ${o.n}${error_or_n(o.n, n)}`)
})
};

/**
* Method 1 :: +1
* insert an extra 'then' before returning
* */
var q = new Promise(function(resolve, reject) {
var o = {n: 'q'};
o.p1 = 'q' + error_or_n('q', '1');
resolve(o)
})
.then(o => {
console.log(o.p1);
return pp(o, 2).then(_ => {
o.p1 = _;
return o
});
})
.then((o) => {
console.log(o.p1);
return pp(o, 3).then(_ => {
o.p1 = _ + '|';
return o
});
})
.then(o => {
console.log(o.p1);
return Promise.resolve(o)
})
.catch(e => console.log(e));

/**
* Method 2 :: +1
* use closures
*
* */
var t = new Promise(function(resolve, reject) {
var o = {n: 't'};
o.p1 = 't' + error_or_n('t', '1');
resolve(o)
})
.then(o => {
console.log(o.p1);
return pp(o, 2)
.then(_ => {
o.p1 = _;
console.log(o.p1);
return pp(o, 3)
})
.then(_ => {
o.p1 = _ + "|";
console.log(o.p1);
return Promise.resolve(o)
})
})
.catch(e => console.log(e));


Promise.all([q, t]).then(_ => {
if(_.every(_ => _)) {
console.log('all done...');
console.dir(_)
}
});
Promise.race([q, t]).then(_ => {
if(_) {
console.log('1\'st done...');
console.dir(_)
}
});

Answer

What you're describing isn't possible. You can't influence the this context of a then handler by doing some manipulation in an earlier then. The this value of a then handler is determined entirely by the function itself (global object/undefined by default, or some value if the function itself is bound ahead of time).

This is declared in the Promises/A+ spec:

onFulfilled and onRejected must be called as functions (i.e. with no this value). [3.2]


If you want to have o easily accessible across multiple thens, you can employ nesting:

var p2 = new Promise(function(resolve, reject) {
  var o = {};
  o.p1 = 't' + error_or_n('t', '1');
  resolve(o)
})
.then(o => {
   console.log(o.p1);

   return pp(o.p1, '-> q' + error_or_n('t', '2'))
     .then( function (_) {
        o.p1 = _;
     })
     .then( function () {
        // do something else with o
     })
     .then( function () {
        // do more with o
     });
})
.catch(e => console.log(e));
Comments