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

Commit 78b6e8a

Browse files
committed
feat($parse): add support for transparent evaluation of Promises
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
1 parent b656552 commit 78b6e8a

File tree

2 files changed

+189
-5
lines changed

2 files changed

+189
-5
lines changed

src/service/parse.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -522,9 +522,21 @@ function parser(text, json, $filter){
522522
consume(']');
523523
return extend(
524524
function(self){
525-
var o = obj(self);
526-
var i = indexFn(self);
527-
return (o) ? o[i] : undefined;
525+
var o = obj(self),
526+
i = indexFn(self),
527+
v, p;
528+
529+
if (!o) return undefined;
530+
v = o[i];
531+
if (v && v.then) {
532+
p = v;
533+
if (!('$$v' in v)) {
534+
p.$$v = undefined;
535+
p.then(function(val) { p.$$v = val; });
536+
}
537+
v = v.$$v;
538+
}
539+
return v;
528540
}, {
529541
assign:function(self, value){
530542
return obj(self)[indexFn(self)] = value;
@@ -673,7 +685,7 @@ function getterFn(path) {
673685
var fn = getterFnCache[path];
674686
if (fn) return fn;
675687

676-
var code = 'var l, fn, t;\n';
688+
var code = 'var l, fn, p;\n';
677689
forEach(path.split('.'), function(key) {
678690
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
679691
code += 'if(!s) return s;\n' +
@@ -683,11 +695,18 @@ function getterFn(path) {
683695
' fn=function(){ return l' + key + '.apply(l, arguments); };\n' +
684696
' fn.$unboundFn=s;\n' +
685697
' s=fn;\n' +
698+
'} else if (s && s.then) {\n' +
699+
' if (!("$$v" in s)) {\n' +
700+
' p=s;\n' +
701+
' p.$$v = undefined;\n' +
702+
' p.then(function(v) {p.$$v=v;});\n' +
703+
'}\n' +
704+
' s=s.$$v\n' +
686705
'}\n';
687706
});
688707
code += 'return s;';
689708
fn = Function('s', code);
690-
fn["toString"] = function() { return code; };
709+
fn.toString = function() { return code; };
691710

692711
return getterFnCache[path] = fn;
693712
}

test/service/parseSpec.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,171 @@ describe('parser', function() {
407407
});
408408

409409

410+
describe('promises', function() {
411+
var deferred, promise, q;
412+
413+
beforeEach(inject(function($q) {
414+
q = $q;
415+
deferred = q.defer();
416+
promise = deferred.promise;
417+
}));
418+
419+
describe('{{promise}}', function() {
420+
it('should evaluated resolved promise and get its value', function() {
421+
deferred.resolve('hello!');
422+
scope.greeting = promise;
423+
expect(scope.$eval('greeting')).toBe(undefined);
424+
scope.$digest();
425+
expect(scope.$eval('greeting')).toBe('hello!');
426+
});
427+
428+
429+
it('should evaluated rejected promise and ignore the rejection reason', function() {
430+
deferred.reject('sorry');
431+
scope.greeting = promise;
432+
expect(scope.$eval('gretting')).toBe(undefined);
433+
scope.$digest();
434+
expect(scope.$eval('greeting')).toBe(undefined);
435+
});
436+
437+
438+
it('should evaluate a promise and eventualy get its value', function() {
439+
scope.greeting = promise;
440+
expect(scope.$eval('greeting')).toBe(undefined);
441+
442+
scope.$digest();
443+
expect(scope.$eval('greeting')).toBe(undefined);
444+
445+
deferred.resolve('hello!');
446+
expect(scope.$eval('greeting')).toBe(undefined);
447+
scope.$digest();
448+
expect(scope.$eval('greeting')).toBe('hello!');
449+
});
450+
451+
452+
it('should evaluate a promise and eventualy ignore its rejection', function() {
453+
scope.greeting = promise;
454+
expect(scope.$eval('greeting')).toBe(undefined);
455+
456+
scope.$digest();
457+
expect(scope.$eval('greeting')).toBe(undefined);
458+
459+
deferred.reject('sorry');
460+
expect(scope.$eval('greeting')).toBe(undefined);
461+
scope.$digest();
462+
expect(scope.$eval('greeting')).toBe(undefined);
463+
});
464+
});
465+
466+
describe('dereferencing', function() {
467+
it('should evaluate and dereference properties leading to and from a promise', function() {
468+
scope.obj = {greeting: promise};
469+
expect(scope.$eval('obj.greeting')).toBe(undefined);
470+
expect(scope.$eval('obj.greeting.polite')).toBe(undefined);
471+
472+
scope.$digest();
473+
expect(scope.$eval('obj.greeting')).toBe(undefined);
474+
expect(scope.$eval('obj.greeting.polite')).toBe(undefined);
475+
476+
deferred.resolve({polite: 'Good morning!'});
477+
scope.$digest();
478+
expect(scope.$eval('obj.greeting')).toEqual({polite: 'Good morning!'});
479+
expect(scope.$eval('obj.greeting.polite')).toBe('Good morning!');
480+
});
481+
482+
it('should evaluate and dereference properties leading to and from a promise via bracket ' +
483+
'notation', function() {
484+
scope.obj = {greeting: promise};
485+
expect(scope.$eval('obj["greeting"]')).toBe(undefined);
486+
expect(scope.$eval('obj["greeting"]["polite"]')).toBe(undefined);
487+
488+
scope.$digest();
489+
expect(scope.$eval('obj["greeting"]')).toBe(undefined);
490+
expect(scope.$eval('obj["greeting"]["polite"]')).toBe(undefined);
491+
492+
deferred.resolve({polite: 'Good morning!'});
493+
scope.$digest();
494+
expect(scope.$eval('obj["greeting"]')).toEqual({polite: 'Good morning!'});
495+
expect(scope.$eval('obj["greeting"]["polite"]')).toBe('Good morning!');
496+
});
497+
498+
499+
it('should evaluate and dereference array references leading to and from a promise',
500+
function() {
501+
scope.greetings = [promise];
502+
expect(scope.$eval('greetings[0]')).toBe(undefined);
503+
expect(scope.$eval('greetings[0][0]')).toBe(undefined);
504+
505+
scope.$digest();
506+
expect(scope.$eval('greetings[0]')).toBe(undefined);
507+
expect(scope.$eval('greetings[0][0]')).toBe(undefined);
508+
509+
deferred.resolve(['Hi!', 'Cau!']);
510+
scope.$digest();
511+
expect(scope.$eval('greetings[0]')).toEqual(['Hi!', 'Cau!']);
512+
expect(scope.$eval('greetings[0][0]')).toBe('Hi!');
513+
});
514+
515+
516+
it('should evaluate and dereference promises used as function arguments', function() {
517+
scope.greet = function(name) { return 'Hi ' + name + '!'; };
518+
scope.name = promise;
519+
expect(scope.$eval('greet(name)')).toBe('Hi undefined!');
520+
521+
scope.$digest();
522+
expect(scope.$eval('greet(name)')).toBe('Hi undefined!');
523+
524+
deferred.resolve('Veronica');
525+
expect(scope.$eval('greet(name)')).toBe('Hi undefined!');
526+
527+
scope.$digest();
528+
expect(scope.$eval('greet(name)')).toBe('Hi Veronica!');
529+
});
530+
531+
532+
it('should evaluate and dereference promises used as array indexes', function() {
533+
scope.childIndex = promise;
534+
scope.kids = ['Adam', 'Veronica', 'Elisa'];
535+
expect(scope.$eval('kids[childIndex]')).toBe(undefined);
536+
537+
scope.$digest();
538+
expect(scope.$eval('kids[childIndex]')).toBe(undefined);
539+
540+
deferred.resolve(1);
541+
expect(scope.$eval('kids[childIndex]')).toBe(undefined);
542+
543+
scope.$digest();
544+
expect(scope.$eval('kids[childIndex]')).toBe('Veronica');
545+
});
546+
547+
548+
it('should evaluate and dereference promises used as keys in bracket notation', function() {
549+
scope.childKey = promise;
550+
scope.kids = {'a': 'Adam', 'v': 'Veronica', 'e': 'Elisa'};
551+
552+
expect(scope.$eval('kids[childKey]')).toBe(undefined);
553+
554+
scope.$digest();
555+
expect(scope.$eval('kids[childKey]')).toBe(undefined);
556+
557+
deferred.resolve('v');
558+
expect(scope.$eval('kids[childKey]')).toBe(undefined);
559+
560+
scope.$digest();
561+
expect(scope.$eval('kids[childKey]')).toBe('Veronica');
562+
});
563+
564+
565+
it('should not mess with the promise if it was not directly evaluated', function() {
566+
scope.obj = {greeting: promise, username: 'hi'};
567+
var obj = scope.$eval('obj');
568+
expect(obj.username).toEqual('hi');
569+
expect(typeof obj.greeting.then).toBe('function');
570+
});
571+
});
572+
});
573+
574+
410575
describe('assignable', function() {
411576
it('should expose assignment function', inject(function($parse) {
412577
var fn = $parse('a');

0 commit comments

Comments
 (0)