blob: 91275be355f34b5bfcd1a4941bd8d94d446be990 [file] [log] [blame]
// Copyright (C) 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Implementation of
* http://wiki.ecmascript.org/doku.php?id=strawman:concurrency
*
* <p>Assumes ES5-strict. Compatible with base ES5-strict or SES. When
* run on SES, has specified security properties.
*/
var Q;
(function(){
"use strict";
var def = typeof cajaVM === 'undefined' ? Object.freeze : cajaVM.def;
var apply = Function.prototype.apply;
var slice = [].slice;
function handle(target) {
var handler = def({
resolution: function() { return target; },
get: function(name) { return target[name]; },
post: function(opt_name, args) {
if (opt_name === void 0) {
return apply.call(target, void 0, args);
}
return apply.call(target[opt_name], target, args);
},
put: function(name, newVal) { target[name] = newVal; },
delete: function(name) {
if (!delete target[name]) {
throw new TypeError('not deleted: ' + name);
}
}
});
return handler;
}
function later(thunk) {
var defer = Q.defer();
setTimeout(function() {
try {
defer.resolve(thunk());
} catch (err) {
defer.resolve(Q.reject(err));
}
}, 0);
return defer.promise;
}
/**
* An encapsulated map from genuine promises to encapsulated well
* behaved trigger functions.
*
* <p>A trigger is a function from an a well behaved success
* continuation and an optional well behaved failure continuation
* to a genuine promise. A success continuation is a function from
* an untrusted handler to a genuine promise. A failure
* continuation is a function from an untrusted error (the reason)
* to a genuine promise.
*
* <p>Well behaved success and failure continuations do not run any
* user code during the current turn, protecting the caller from
* interleaving. A well behaved trigger given well behaved success
* and failure continuations also does not run any user code during
* the current turn. For each call to a trigger, a well behaved
* trigger will call no more than one of its arguments and call it
* nore more than once.
*
* <p>Invariants: Only genuine promises and well behaved trigger
* functions are stored here, and these trigger functions are only
* ever called with well behaved success and failure
* continuations.
*/
var triggers = WeakMap();
function getTrigger(target) {
if (target === Object(target)) { return triggers.get(target); }
return void 0;
}
function Promise(trigger) {
var promise = def({
get: function(name) {
return trigger(function(handler) {
return later(function() { return handler.get(name); });
});
},
post: function(opt_name, args) {
return trigger(function(handler) {
return later(function() { return handler.post(opt_name, args); });
});
},
send: function(opt_name, var_args) {
return promise.post(opt_name, slice.call(arguments, 1));
},
put: function(name, newVal) {
return trigger(function(handler) {
return later(function() { return handler.put(name, newVal); });
});
},
delete: function(name) {
return trigger(function(handler) {
return later(function() { return handler.delete(name); });
});
},
when: function(callback, opt_errback) {
return trigger(function(handler) {
return later(function() {
var r = handler.resolution();
if (Q.isPromise(r)) {
// Then this can't be the built-in handler. TODO(erights):
// Need some way to distinguish whether it should be
// treated as a handler for an unresolved remote promise
// or a far reference (which is therefore
// fulfilled). Until then, treat as an far
// reference. TODO(erights): This isn't yet even good
// enough for a far reference since it has no way to
// spontaneously transition to broken.
r = Q.makePromise(handler);
}
return callback(r);
});
}, function(reason) {
var errback = opt_errback || Q.reject;
return later(function() { return errback(reason); });
});
},
end: function() {
trigger(function(handler) {}, function(reason) {
// log the reason to wherever uncaught exceptions go on
// this platform, e.g., onerror(reason).
setTimeout(function() { throw reason; }, 0);
});
return promise;
}
});
triggers.set(promise, trigger);
return promise;
};
Q = function Q(target) {
if (Q.isPromise(target)) { return target; }
return Q.makePromise(handle(target));
};
Q.reject = function(reason) {
var rejection = Promise(function(sk, opt_fk) {
if (opt_fk) { return opt_fk(reason); }
return rejection;
});
};
Q.defer = function() {
var ks = [];
var trigger = function(sk, opt_fk) {
var fk = opt_fk || Q.reject;
var deferred = Q.defer();
ks.push({
sk: function(handler) { deferred.resolve(sk(handler)); },
fk: function(reason) { deferred.resolve(fk)(reason); }
});
return deferred.promise;
};
var promise = Promise(function(sk, opt_fk) {
return trigger(sk, opt_fk);
});
var resolve = function(target) {
target = Q(target);
trigger = getTrigger(target);
triggers.set(promise, trigger); // only a cute optimization
resolve = function(target) {}; // throw error?
ks.forEach(function(k) { trigger(k.sk, k.fk); });
ks = void 0; // help gc
};
return def({
promise: promise,
resolve: function(next) { return resolve(next); }
});
};
Q.isPromise = function(target) { return !!getTrigger(target); };
Q.makePromise = function(handler) {
return Promise(function(sk, opt_fk) { return sk(handler); });
};
// Q.near is no longer possible, which is probably ok.
def(Q);
})();
function show(print, target) {
if (Q.isPromise(target)) {
target.when(function(v) { print('ok: ' + v); },
function(err) { print('bad: ' + err); });
} else {
print('now: ' + target);
}
}
// var sh = show.bind(void 0, console.log.bind(console));