| // 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)); |