Kyle Richardson
Kyle Richardson

Reputation: 5645

Attaching an attribute to a Promise

Another question on SO perked my interest in creating a setTimeout that returned a Promise and a timerId, yet would not break backwards compatibility. This is the question I am referring to.

It simply asks if the inner return statement in the _.delay method of underscore.js is extraneous or serves a purpose. Here is the block of code.

This is the code for _.delay:

// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function() { // this is to return the timerId
        return func.apply(null, args); // this guy right is in question
    }, wait);
};

Considering setTimeout does not currently return a Promise, I proposed the idea that it is possibly there for future proofing, incase setTimeout does one day return a promise.

To do this setTimeout would need to return a timerId so that it can be canceled. So to return a Promise you would need to attach the timerId to the Promise, so that the Promise is returned and the timerId is accessable.

You could then modify clearTimeout to perform exactly how it does now when given a timerId, but for a Promise it uses Promise.timerId to clear the timeout and cancel the Promise. Of course Promise canceling would need to be implemented as well...

Anyhow... I started working on something for fun and came across something I haven't been able to explain. If you run the snippet below, you will see that the promise has .timerId attribute before it is returned, but the attribute is missing after the return. Can anyone explain this?

function pseudoSetTimeout( func, wait ) {

    let timerId,
        promise = new Promise( ( resolve, reject ) => {
            timerId = setTimeout( () => {
                let returnVal = func();
                resolve( returnVal );
            }, wait );
        });

    promise.timerId = timerId;

    console.log( "promise before return: ", promise );

    return promise;
}

function callback() {
    return "Callback fired";
}

let timeout = pseudoSetTimeout( callback, 1000 )
    .then( ( val ) => {
      console.log( val );
    });

console.log( "returned promise: ", timeout );

Upvotes: 6

Views: 2471

Answers (3)

Dave Cousineau
Dave Cousineau

Reputation: 13198

Due to the reasons mentioned in the other answers, I would return a 'tuple' instead of using a property on the promise:

function pseudoSetTimeout( func, wait ) {
    let timerId,
        promise = new Promise( ( resolve, reject ) => {
            timerId = setTimeout( () => {
                let returnVal = func();
                resolve( returnVal );
            }, wait );
        });

    let result = [timerId, promise];
    console.log ( "returning: ", result );
    return result;
}

function callback() {
    return "Callback fired";
}

let [timeout, promise] = pseudoSetTimeout( callback, 1000 );

promise.then( ( val ) => {
   console.log( val );
});

console.log( "returned timeout: ", timeout );

Upvotes: 0

trincot
trincot

Reputation: 351074

The then in:

pseudoSetTimeout( callback, 1000 )
    .then( ( val ) => {
      console.log( val );
    });

returns a new promise, which is not the one that pseudoSetTimeout returned. It is a promise with undefined as promised value, since the then callback does not return anything.

You could make it work by not applying then during the assignment:

function pseudoSetTimeout( func, wait ) {

    let timerId,
        promise = new Promise( ( resolve, reject ) => {
            timerId = setTimeout( () => {
                let returnVal = func();
                resolve( returnVal );
            }, wait );
        });

    promise.timerId = timerId;

    console.log( "promise before return: ", promise );

    return promise;
}

function callback() {
    return "Callback fired";
}

let timeout = pseudoSetTimeout( callback, 1000 );
timeout.then( ( val ) => {
  console.log( val );
});

console.log( "returned promise: ", timeout );

Because then is crucial in the use of promises, and is often used in chaining them, it seems that the idea to attach a custom property to a promise loses on usefulness.

Upvotes: 5

Boris Charpentier
Boris Charpentier

Reputation: 3543

Really interesting question.

Not sure of how it's made internally in node, but if looking on the then code of bluebird is any indication : https://github.com/petkaantonov/bluebird/blob/master/src/thenables.js

My guess would be that the Promise returned is actually another object.

Upvotes: 0

Related Questions