1.2.0 • Published 9 years ago

q-local v1.2.0

Weekly downloads
2
License
MIT
Repository
github
Last release
9 years ago

Q with local data context

This is a fork of Kristopher Kowal's Q with one difference -- this version of the library allows data to be shared easily between promises, as long as they are part of the same promise chain or any of its subchains.

Why not use function scope?

Function scope is a great approach when your code allows it. For example:

function run()
{
    var myVariable;
    
    Q(true)
    .then(function() {
        return Q(true)
        .then(function() {
            myVariable = 12;
        });
    })
    .then(function() {
        return Q(true)
        .then(function() {
            console.log(myVariable) // outputs "12"
        });
    });
}

That works brilliantly. However, function scope is not always available. Consider the following:

var ClassA = function() {};

ClassA.prototype.setVariable = function() {
    return function() {
        return Q(true)
        .then(function() {
            myVariable = 12; // Undefined variable error
        });
    };
};

module.exports = ClassA;
var ClassB = function() {};

ClassB.prototype.logVariable = function() {
    return function() {
        return Q(true)
        .then(function() {
            console.log(myVariable); // Undefined variable error
        });
    };
};

module.exports = ClassB;
var ClassA = require('./class-a'),
    ClassB = require('./class-b');

function run2()
{
    var myVariable,
        a = new ClassA(),
        b = new ClassB();

    Q(true).then(a.setVariable()).then(b.logVariable());
}

Since setVariable() and logVariable() do not share context with run(), myVariable will remain undefined within ClassA and ClassB.

Why not pass variables as function arguments?

Standard approach to fix the code above would be to pass myVariable to the objects eiter as an argument in the function calls or in their constructors. This works well, provided that you have access to the variable(s) at all times when you need to use them. However, it also forces you to pass the variables to different parts of your chain, and may even force you to pass the variables through chains that have nothing to do with your variables just so that you can get to the variables at the parts where they are needed.

When dealing with long promise chains and, particularly, subchains generated by code in multiple modules, passing variables down the promise chains gets very cumbersome and increases the likelihood of human error, as accidentally omitting one variable from being passed will fail all downstream parts of the chain relying on that variable.

Is there a better approach?

If you are dealing with relatively simple promise chains, the two strategies described above should satisfy your needs, despite their limitations.

The local data context is a solution for complex promise chains, in which isolated promises in the same chain (or in separate subchains which are connected through a superchain) need to share data between each other.

Instead of forcing developers to pass arguments through the chain manually, Q-Local introduces an new function promise.local(function(localData) {}), which shares the localData object across all members of the promise chain and subchains.

Note that unconnected chains will not share the same localData object -- each unconnected chain will have its own localData object.

Setting values

Q(true)
.local(function(localData) {
    localData.myVariable = 1001;
});

Getting values

Q(true)
.local(function(localData) {
    // (Will print out 'undefined', unless connected to the previous chain, of course)
    console.log(localData.myVariable);
});

Previous examples in local data flavor

Function context:

function run()
{
    var myVariable;
    
    Q(true)
    .then(function() {
        return Q(true)
        .local(function(localData) {
            localData.myVariable = 12;
        });
    })
    .then(function() {
        return Q(true)
        .local(function(localData) {
            console.log(localData.myVariable); // outputs "12"
        });
    });
}

Modules:

var ClassA = function() {};

ClassA.prototype.setVariable = function() {
    return function() {
        return Q(true)
        .local(function(localData) {
            localData.myVariable = 12;
        });
    };
};

module.exports = ClassA;
var ClassB = function() {};

ClassB.prototype.logVariable = function() {
    return function() {
        return Q(true)
        .local(function(localData) {
            console.log(localData.myVariable);
        });
    };
};

module.exports = ClassB;
var ClassA = require('./class-a'),
    ClassB = require('./class-b');

function run2()
{
    var myVariable,
        a = new ClassA(),
        b = new ClassB();

    Q(true).then(a.setVariable()).then(b.logVariable());
}

API Reference

promise.local(onAccess)

Exposes the shared local data object to onAccess function. The local data object will be passed as the first argument of the function call.

function onAccess(localData) {}

Any changes to the properties of localData will be automatically shared with other accessors. Note that changing the localData object itself (e.g. localData = {};) will not have any effect outside of onAccess function.

The return value of onAccess will be treated as if it was promise.then(onAccess, null, null).

Execution order behaves the same way than promise.then(onAccess, null, null)

License

Changes in Q Local are copyright 2014-2015 Aleksi Asikainen.

Q is Copyright 2009–2015 Kristopher Michael Kowal.

MIT License (enclosed)