Skip to content

Commit

Permalink
cleans up can.Compute.read and adds template-obserbable promises for #…
Browse files Browse the repository at this point in the history
  • Loading branch information
justinbmeyer committed Mar 22, 2015
1 parent 61ebdb6 commit 592377c
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 106 deletions.
28 changes: 28 additions & 0 deletions compute/compute_test.js
Expand Up @@ -758,5 +758,33 @@ steal("can/compute", "can/test", "can/map", "steal-qunit", function () {
equal(combined(), true);

});

test("can.Compute.read can read a promise (#179)", function(){

var def = new can.Deferred();
var map = new can.Map();

var c = can.compute(function(){
return can.Compute.read({map: map},["map","data","value"]).value;
});

var calls = 0;
c.bind("change", function(ev, newVal, oldVal){
calls++;
equal(calls, 1, "only one call");
equal(newVal, "Something", "new value");
equal(oldVal, undefined, "oldVal");
start();
});

map.attr("data", def);

setTimeout(function(){
def.resolve("Something");
},2);

stop();

});

});
111 changes: 6 additions & 105 deletions compute/proto_compute.js
@@ -1,4 +1,4 @@
steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) {
steal('can/util', 'can/util/bind', 'can/compute/read.js','can/util/batch', function (can, bind, read) {
var stack = [];

can.__read = function (func, self) {
Expand Down Expand Up @@ -179,12 +179,10 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) {
};
};

var isObserve = function (obj) {
return obj instanceof can.Map || obj && obj.__get;
},

// Instead of calculating whether anything is listening every time,
// use a function to do nothing (which may be overwritten)
k = function () {};
var k = function () {};

var updater = function(newVal, oldVal, batchNum) {
this.setCached(newVal);
Expand Down Expand Up @@ -532,105 +530,8 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) {
});
};

can.Compute.read = function (parent, reads, options) {
options = options || {};
// `cur` is the current value.
var cur = parent,
type,
// `prev` is the object we are reading from.
prev,
// `foundObs` did we find an observable.
foundObs;
for (var i = 0, readLength = reads.length; i < readLength; i++) {
// Update what we are reading from.
prev = cur;
// Read from the compute. We can't read a property yet.
if (prev && prev.isComputed) {
if (options.foundObservable) {
options.foundObservable(prev, i);
}
prev = cur = prev instanceof can.Compute ? prev.get() : prev();
}
// Look to read a property from something.
if (isObserve(prev)) {
if (!foundObs && options.foundObservable) {
options.foundObservable(prev, i);
}
foundObs = 1;
// is it a method on the prototype?
if (typeof prev[reads[i]] === 'function' && prev.constructor.prototype[reads[i]] === prev[reads[i]]) {
// call that method
if (options.returnObserveMethods) {
cur = cur[reads[i]];
} else if ( (reads[i] === 'constructor' && prev instanceof can.Construct) ||
(prev[reads[i]].prototype instanceof can.Construct)) {
cur = prev[reads[i]];
} else {
cur = prev[reads[i]].apply(prev, options.args || []);
}
} else {
// use attr to get that value
cur = cur.attr(reads[i]);
}
} else {
// just do the dot operator
if(cur == null) {
cur = undefined;
} else {
cur = prev[reads[i]];
}

}
type = typeof cur;
// If it's a compute, get the compute's value
// unless we are at the end of the
if (cur && cur.isComputed && (!options.isArgument && i < readLength - 1)) {
if (!foundObs && options.foundObservable) {
options.foundObservable(prev, i + 1);
}
cur = cur();
}
// If it's an anonymous function, execute as requested
else if (i < reads.length - 1 && type === 'function' && options.executeAnonymousFunctions && !(can.Construct && cur.prototype instanceof can.Construct)) {
cur = cur();
}
// if there are properties left to read, and we don't have an object, early exit
if (i < reads.length - 1 && (cur === null || type !== 'function' && type !== 'object')) {
if (options.earlyExit) {
options.earlyExit(prev, i, cur);
}
// return undefined so we know this isn't the right value
return {
value: undefined,
parent: prev
};
}
}
// handle an ending function
// unless it is a can.Construct-derived constructor
if (typeof cur === 'function' && !(can.Construct && cur.prototype instanceof can.Construct) && !(can.route && cur === can.route)) {
if (options.isArgument) {
if (!cur.isComputed && options.proxyMethods !== false) {
cur = can.proxy(cur, prev);
}
} else {
if (cur.isComputed && !foundObs && options.foundObservable) {
options.foundObservable(cur, i);
}
cur = cur.call(prev);
}
}
// if we don't have a value, exit early.
if (cur === undefined) {
if (options.earlyExit) {
options.earlyExit(prev, i - 1);
}
}
return {
value: cur,
parent: prev
};
};
can.Compute.read = read;

can.Compute.truthy = function(compute) {
return new can.Compute(function() {
var res = compute.get();
Expand All @@ -642,7 +543,7 @@ steal('can/util', 'can/util/bind', 'can/util/batch', function (can, bind) {
};

can.Compute.set = function(parent, key, value) {
if(isObserve(parent)) {
if(can.isMapLike(parent)) {
return parent.attr(key, value);
}

Expand Down
200 changes: 200 additions & 0 deletions compute/read.js
@@ -0,0 +1,200 @@
steal("can/util", function(can){




// there are things that you need to evaluate when you get them back as a property read
// for example a compute or a function you might need to call to get the next value to
// actually check

var read = function (parent, reads, options) {
options = options || {};
var state = {
foundObservable: false
};

// `cur` is the current value.
var cur = readValue(parent, 0, reads, options, state),
type,
// `prev` is the object we are reading from.
prev,
// `foundObs` did we find an observable.
foundObs,
readLength = reads.length,
i = 0;

while( i < readLength ) {
prev = cur;
// try to read the property
for(var r=0, readersLength = read.propertyReaders.length; r < readersLength; r++) {
var reader = read.propertyReaders[r];
if(reader.test(cur)) {
cur = reader.read(cur, reads[i], i, options, state);
break; // there can be only one reading of a property
}
}
i = i+1;
// read the value if it is a compute or function
cur = readValue(cur, i, reads, options, state);
type = typeof cur;
// early exit if need be
if (i < reads.length && (cur === null || type !== 'function' && type !== 'object')) {
if (options.earlyExit) {
options.earlyExit(prev, i - 1, cur);
}
// return undefined so we know this isn't the right value
return {
value: undefined,
parent: prev
};
}

}


// handle an ending function
// unless it is a can.Construct-derived constructor
if (typeof cur === 'function' && !(can.Construct && cur.prototype instanceof can.Construct) && !(can.route && cur === can.route)) {
if (options.isArgument) {
if (!cur.isComputed && options.proxyMethods !== false) {
cur = can.proxy(cur, prev);
}
} else {
if (cur.isComputed && !foundObs && options.foundObservable) {
options.foundObservable(cur, i);
}
cur = cur.call(prev);
}
}
// if we don't have a value, exit early.
if (cur === undefined) {
if (options.earlyExit) {
options.earlyExit(prev, i - 1);
}
}
return {
value: cur,
parent: prev
};
};

var readValue = function(value, index, reads, options, state){
for(var i =0, len = read.valueReaders.length; i < len; i++){
if( read.valueReaders[i].test(value, index, reads, options) ) {
value = read.valueReaders[i].read(value, index, reads, options, state);
}
}
return value;
};
// value readers check the current value
// and get a new value from it
// ideally they would keep calling until
// none of these passed
read.valueReaders = [{
// compute value reader
test: function(value, i, reads, options){
return value && value.isComputed && (!options.isArgument && i < reads.length );
},
read: function(value, i, reads, options, state){
if (!state.foundObservable && options.foundObservable) {
options.foundObservable(value, i);
state.foundObservable = true;
}
return value instanceof can.Compute ? value.get() : value();
}
},{
// if this is a function before the last read and its not a constructor function
test: function(value, i, reads, options){
var type = typeof value;
// i = reads.length if this is the last iteration of the read for-loop.
return i < reads.length &&
type === 'function' &&
options.executeAnonymousFunctions &&
!(can.Construct && value.prototype instanceof can.Construct);
},
read: function(value){
return value();
}
}];

// propertyReaders actually read a property value
read.propertyReaders = [
// read a can.Map or can.route
{
test: can.isMapLike,
read: function(value, prop, index, options, state){
if (!state.foundObservable && options.foundObservable) {
options.foundObservable(value, index);
state.foundObservable = true;
}
if (typeof value[prop] === 'function' && value.constructor.prototype[prop] === value[prop]) {
// call that method
if (options.returnObserveMethods) {
return value[prop];
// if the property value is a can.Construct
} else if ( (prop === 'constructor' && value instanceof can.Construct) ||
(value[prop].prototype instanceof can.Construct)) {
return value[prop];
} else {
return value[prop].apply(value, options.args || []);
}
} else {
// use attr to get that value
return value.attr(prop);
}
}
},
// read a promise
{
test: can.isPromise,
read: function(value, prop, index, options, state){
if (!state.foundObservable && options.foundObservable) {
options.foundObservable(value, index);
state.foundObservable = true;
}
var observeData = value.__observeData;
if(!value.__observeData) {
var observeData = value.__observeData = {
isPending: true,
state: "pending",
isResolved: false,
isRejected: false
};
// proto based would be faster
can.simpleExtend(observeData, can.event);
value.then(function(value){
observeData.isPending = false;
observeData.isResolved = true;
observeData.value = value;
observeData.state = "resolved";
observeData.dispatch("state",["resolved","pending"]);
}, function(reason){
observeData.isPending = false;
observeData.isRejected = true;
observeData.reason = reason;
observeData.state = "rejected";
observeData.dispatch("state",["rejected","pending"]);
});
}
can.__reading(observeData,"state");
return observeData[prop];
}
},

// read a normal object
{
// this is the default
test: function(){return true;},
read: function(value, prop){
if(value == null) {
return undefined;
} else {
return value[prop];
}
}
}
];


return read;
});
5 changes: 4 additions & 1 deletion util/can.js
Expand Up @@ -12,10 +12,13 @@ steal(function () {
// An empty function useful for where you need a dummy callback.
can.k = function(){};

can.isDeferred = function (obj) {
can.isDeferred = can.isPromise = function (obj) {
// Returns `true` if something looks like a deferred.
return obj && typeof obj.then === "function" && typeof obj.pipe === "function";
};
can.isMapLike = function(obj){
return can.Map && (obj instanceof can.Map || obj && obj.__get);
};

var cid = 0;
can.cid = function (object, name) {
Expand Down

0 comments on commit 592377c

Please sign in to comment.