Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($parse): add support for transparent evaluation of Promises
Browse files Browse the repository at this point in the history
Parser now builds expressions that can detect promises and transparently
evaluate them to undefined or the promise value.

If promiseA is resolved with value 'A', then {{promiseA}} evals to 'A';
If promiseA is unresolved, then {{promiseA}} evals to undefined;

Following invocations are supported:

- {{promise}}
- {{promise.futureProp}}
- {{[promise][0]}}
- {{object.promise}}
- {{object[promise]}}
- {{array[promise]}}
- {{fn(promise)}}
- combinations of the above
  • Loading branch information
IgorMinar committed Nov 30, 2011
1 parent b656552 commit 78b6e8a
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 5 deletions.
29 changes: 24 additions & 5 deletions src/service/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -522,9 +522,21 @@ function parser(text, json, $filter){
consume(']');
return extend(
function(self){
var o = obj(self);
var i = indexFn(self);
return (o) ? o[i] : undefined;
var o = obj(self),
i = indexFn(self),
v, p;

if (!o) return undefined;
v = o[i];
if (v && v.then) {
p = v;
if (!('$$v' in v)) {
p.$$v = undefined;
p.then(function(val) { p.$$v = val; });
}
v = v.$$v;
}
return v;
}, {
assign:function(self, value){
return obj(self)[indexFn(self)] = value;
Expand Down Expand Up @@ -673,7 +685,7 @@ function getterFn(path) {
var fn = getterFnCache[path];
if (fn) return fn;

var code = 'var l, fn, t;\n';
var code = 'var l, fn, p;\n';
forEach(path.split('.'), function(key) {
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
code += 'if(!s) return s;\n' +
Expand All @@ -683,11 +695,18 @@ function getterFn(path) {
' fn=function(){ return l' + key + '.apply(l, arguments); };\n' +
' fn.$unboundFn=s;\n' +
' s=fn;\n' +
'} else if (s && s.then) {\n' +
' if (!("$$v" in s)) {\n' +
' p=s;\n' +
' p.$$v = undefined;\n' +
' p.then(function(v) {p.$$v=v;});\n' +
'}\n' +
' s=s.$$v\n' +
'}\n';
});
code += 'return s;';
fn = Function('s', code);
fn["toString"] = function() { return code; };
fn.toString = function() { return code; };

return getterFnCache[path] = fn;
}
Expand Down
165 changes: 165 additions & 0 deletions test/service/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,171 @@ describe('parser', function() {
});


describe('promises', function() {
var deferred, promise, q;

beforeEach(inject(function($q) {
q = $q;
deferred = q.defer();
promise = deferred.promise;
}));

describe('{{promise}}', function() {
it('should evaluated resolved promise and get its value', function() {
deferred.resolve('hello!');
scope.greeting = promise;
expect(scope.$eval('greeting')).toBe(undefined);
scope.$digest();
expect(scope.$eval('greeting')).toBe('hello!');
});


it('should evaluated rejected promise and ignore the rejection reason', function() {
deferred.reject('sorry');
scope.greeting = promise;
expect(scope.$eval('gretting')).toBe(undefined);
scope.$digest();
expect(scope.$eval('greeting')).toBe(undefined);
});


it('should evaluate a promise and eventualy get its value', function() {
scope.greeting = promise;
expect(scope.$eval('greeting')).toBe(undefined);

scope.$digest();
expect(scope.$eval('greeting')).toBe(undefined);

deferred.resolve('hello!');
expect(scope.$eval('greeting')).toBe(undefined);
scope.$digest();
expect(scope.$eval('greeting')).toBe('hello!');
});


it('should evaluate a promise and eventualy ignore its rejection', function() {
scope.greeting = promise;
expect(scope.$eval('greeting')).toBe(undefined);

scope.$digest();
expect(scope.$eval('greeting')).toBe(undefined);

deferred.reject('sorry');
expect(scope.$eval('greeting')).toBe(undefined);
scope.$digest();
expect(scope.$eval('greeting')).toBe(undefined);
});
});

describe('dereferencing', function() {
it('should evaluate and dereference properties leading to and from a promise', function() {
scope.obj = {greeting: promise};
expect(scope.$eval('obj.greeting')).toBe(undefined);
expect(scope.$eval('obj.greeting.polite')).toBe(undefined);

scope.$digest();
expect(scope.$eval('obj.greeting')).toBe(undefined);
expect(scope.$eval('obj.greeting.polite')).toBe(undefined);

deferred.resolve({polite: 'Good morning!'});
scope.$digest();
expect(scope.$eval('obj.greeting')).toEqual({polite: 'Good morning!'});
expect(scope.$eval('obj.greeting.polite')).toBe('Good morning!');
});

it('should evaluate and dereference properties leading to and from a promise via bracket ' +
'notation', function() {
scope.obj = {greeting: promise};
expect(scope.$eval('obj["greeting"]')).toBe(undefined);
expect(scope.$eval('obj["greeting"]["polite"]')).toBe(undefined);

scope.$digest();
expect(scope.$eval('obj["greeting"]')).toBe(undefined);
expect(scope.$eval('obj["greeting"]["polite"]')).toBe(undefined);

deferred.resolve({polite: 'Good morning!'});
scope.$digest();
expect(scope.$eval('obj["greeting"]')).toEqual({polite: 'Good morning!'});
expect(scope.$eval('obj["greeting"]["polite"]')).toBe('Good morning!');
});


it('should evaluate and dereference array references leading to and from a promise',
function() {
scope.greetings = [promise];
expect(scope.$eval('greetings[0]')).toBe(undefined);
expect(scope.$eval('greetings[0][0]')).toBe(undefined);

scope.$digest();
expect(scope.$eval('greetings[0]')).toBe(undefined);
expect(scope.$eval('greetings[0][0]')).toBe(undefined);

deferred.resolve(['Hi!', 'Cau!']);
scope.$digest();
expect(scope.$eval('greetings[0]')).toEqual(['Hi!', 'Cau!']);
expect(scope.$eval('greetings[0][0]')).toBe('Hi!');
});


it('should evaluate and dereference promises used as function arguments', function() {
scope.greet = function(name) { return 'Hi ' + name + '!'; };
scope.name = promise;
expect(scope.$eval('greet(name)')).toBe('Hi undefined!');

scope.$digest();
expect(scope.$eval('greet(name)')).toBe('Hi undefined!');

deferred.resolve('Veronica');
expect(scope.$eval('greet(name)')).toBe('Hi undefined!');

scope.$digest();
expect(scope.$eval('greet(name)')).toBe('Hi Veronica!');
});


it('should evaluate and dereference promises used as array indexes', function() {
scope.childIndex = promise;
scope.kids = ['Adam', 'Veronica', 'Elisa'];
expect(scope.$eval('kids[childIndex]')).toBe(undefined);

scope.$digest();
expect(scope.$eval('kids[childIndex]')).toBe(undefined);

deferred.resolve(1);
expect(scope.$eval('kids[childIndex]')).toBe(undefined);

scope.$digest();
expect(scope.$eval('kids[childIndex]')).toBe('Veronica');
});


it('should evaluate and dereference promises used as keys in bracket notation', function() {
scope.childKey = promise;
scope.kids = {'a': 'Adam', 'v': 'Veronica', 'e': 'Elisa'};

expect(scope.$eval('kids[childKey]')).toBe(undefined);

scope.$digest();
expect(scope.$eval('kids[childKey]')).toBe(undefined);

deferred.resolve('v');
expect(scope.$eval('kids[childKey]')).toBe(undefined);

scope.$digest();
expect(scope.$eval('kids[childKey]')).toBe('Veronica');
});


it('should not mess with the promise if it was not directly evaluated', function() {
scope.obj = {greeting: promise, username: 'hi'};
var obj = scope.$eval('obj');
expect(obj.username).toEqual('hi');
expect(typeof obj.greeting.then).toBe('function');
});
});
});


describe('assignable', function() {
it('should expose assignment function', inject(function($parse) {
var fn = $parse('a');
Expand Down

0 comments on commit 78b6e8a

Please sign in to comment.