Update to latest jsonld.js (use new Promise API).
authorDave Longley <dlongley@digitalbazaar.com>
Mon, 08 Jul 2013 14:51:22 -0400
changeset 1752 b8660d0d556d
parent 1751 529e83e29bb8
child 1753 759622f419d4
Update to latest jsonld.js (use new Promise API).
playground/Promise.js
playground/index.html
playground/jsonld.js
playground/playground.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/playground/Promise.js	Mon Jul 08 14:51:22 2013 -0400
@@ -0,0 +1,415 @@
+// Copyright (C) 2013:
+//    Alex Russell <slightlyoff@chromium.org>
+//    Yehuda Katz
+//
+// Use of this source code is governed by
+//    http://www.apache.org/licenses/LICENSE-2.0
+
+// FIXME(slightlyoff):
+//    - Document "npm test"
+//    - Change global name from "Promise" to something less conflicty
+(function(global, browserGlobal, underTest) {
+"use strict";
+
+// FIXME(slighltyoff):
+//  * aggregates + tests
+//  * check on fast-forwarding
+
+underTest = !!underTest;
+
+//
+// Async Utilities
+//
+
+// Borrowed from RSVP.js
+var async;
+
+var MutationObserver = browserGlobal.MutationObserver ||
+                       browserGlobal.WebKitMutationObserver;
+var Promise;
+
+if (typeof process !== 'undefined' &&
+  {}.toString.call(process) === '[object process]') {
+  async = function(callback, binding) {
+    process.nextTick(function() {
+      callback.call(binding);
+    });
+  };
+} else if (MutationObserver) {
+  var queue = [];
+
+  var observer = new MutationObserver(function() {
+    var toProcess = queue.slice();
+    queue = [];
+    toProcess.forEach(function(tuple) {
+      tuple[0].call(tuple[1]);
+    });
+  });
+
+  var element = document.createElement('div');
+  observer.observe(element, { attributes: true });
+
+  // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
+  window.addEventListener('unload', function(){
+    observer.disconnect();
+    observer = null;
+  });
+
+  async = function(callback, binding) {
+    queue.push([callback, binding]);
+    element.setAttribute('drainQueue', 'drainQueue');
+  };
+} else {
+  async = function(callback, binding) {
+    setTimeout(function() {
+      callback.call(binding);
+    }, 1);
+  };
+}
+
+//
+// Object Model Utilities
+//
+
+// defineProperties utilities
+var _readOnlyProperty = function(v) {
+    return {
+      enumerable: true,
+      configurable: false,
+      get: v
+    };
+};
+
+var _method = function(v, e, c, w) {
+    return {
+      enumerable:   !!(e || 0),
+      configurable: !!(c || 1),
+      writable:     !!(w || 1),
+      value:           v || function() {}
+    };
+};
+
+var _pseudoPrivate = function(v) { return _method(v, 0, 1, 0); };
+var _public = function(v) { return _method(v, 1); };
+
+//
+// Promises Utilities
+//
+
+var isThenable = function(any) {
+  try {
+    var f = any.then;
+    if (typeof f == "function") {
+      return true;
+    }
+  } catch (e) { /*squelch*/ }
+  return false;
+};
+
+var AlreadyResolved = function(name) {
+  Error.call(this, name);
+};
+AlreadyResolved.prototype = Object.create(Error.prototype);
+
+var Backlog = function() {
+  var bl = [];
+  bl.pump = function(value) {
+    async(function() {
+      var l = bl.length;
+      var x = 0;
+      while(x < l) {
+        x++;
+        bl.shift()(value);
+      }
+    });
+  };
+  return bl;
+};
+
+//
+// Resolver Constuctor
+//
+
+var Resolver = function(future,
+                        fulfillCallbacks,
+                        rejectCallbacks,
+                        setValue,
+                        setError,
+                        setState) {
+  var isResolved = false;
+
+  var resolver = this;
+  var fulfill = function(value) {
+    // console.log("queueing fulfill with:", value);
+    async(function() {
+      setState("fulfilled");
+      setValue(value);
+      // console.log("fulfilling with:", value);
+      fulfillCallbacks.pump(value);
+    });
+  };
+  var reject = function(reason) {
+    // console.log("queuing reject with:", reason);
+    async(function() {
+      setState("rejected");
+      setError(reason);
+      // console.log("rejecting with:", reason);
+      rejectCallbacks.pump(reason);
+    });
+  };
+  var resolve = function(value) {
+    if (isThenable(value)) {
+      value.then(resolve, reject);
+      return;
+    }
+    fulfill(value);
+  };
+  var ifNotResolved = function(func, name) {
+    return function(value) {
+      if (!isResolved) {
+        isResolved = true;
+        func(value);
+      } else {
+        if (typeof console != "undefined") {
+          console.error("Cannot resolve a Promise multiple times.");
+        }
+      }
+    };
+  };
+
+  // Indirectly resolves the Promise, chaining any passed Promise's resolution
+  this.resolve = ifNotResolved(resolve, "resolve");
+
+  // Directly fulfills the future, no matter what value's type is
+  this.fulfill = ifNotResolved(fulfill, "fulfill");
+
+  // Rejects the future
+  this.reject = ifNotResolved(reject, "reject");
+
+  this.cancel  = function() { resolver.reject(new Error("Cancel")); };
+  this.timeout = function() { resolver.reject(new Error("Timeout")); };
+
+  if (underTest) {
+    Object.defineProperties(this, {
+      _isResolved: _readOnlyProperty(function() { return isResolved; }),
+    });
+  }
+
+  setState("pending");
+};
+
+//
+// Promise Constuctor
+//
+
+var Promise = function(init) {
+  var fulfillCallbacks = new Backlog();
+  var rejectCallbacks = new Backlog();
+  var value;
+  var error;
+  var state = "pending";
+
+  if (underTest) {
+    Object.defineProperties(this, {
+      _value: _readOnlyProperty(function() { return value; }),
+      _error: _readOnlyProperty(function() { return error; }),
+      _state: _readOnlyProperty(function() { return state; }),
+    });
+  }
+
+  Object.defineProperties(this, {
+    _addAcceptCallback: _pseudoPrivate(
+      function(cb) {
+        // console.log("adding fulfill callback:", cb);
+        fulfillCallbacks.push(cb);
+        if (state == "fulfilled") {
+          fulfillCallbacks.pump(value);
+        }
+      }
+    ),
+    _addRejectCallback: _pseudoPrivate(
+      function(cb) {
+        // console.log("adding reject callback:", cb);
+        rejectCallbacks.push(cb);
+        if (state == "rejected") {
+          rejectCallbacks.pump(error);
+        }
+      }
+    ),
+  });
+  var r = new Resolver(this,
+                       fulfillCallbacks, rejectCallbacks,
+                       function(v) { value = v; },
+                       function(e) { error = e; },
+                       function(s) { state = s; })
+  try {
+    if (init) { init(r); }
+  } catch(e) {
+    r.reject(e);
+  }
+};
+
+//
+// Consructor
+//
+
+var isCallback = function(any) {
+  return (typeof any == "function");
+};
+
+// Used in .then()
+var wrap = function(callback, resolver, disposition) {
+  if (!isCallback(callback)) {
+    // If we don't get a callback, we want to forward whatever resolution we get
+    return resolver[disposition].bind(resolver);
+  }
+
+  return function() {
+    try {
+      var r = callback.apply(null, arguments);
+      resolver.resolve(r);
+    } catch(e) {
+      // Exceptions reject the resolver
+      resolver.reject(e);
+    }
+  };
+};
+
+var addCallbacks = function(onfulfill, onreject, scope) {
+  if (isCallback(onfulfill)) {
+    scope._addAcceptCallback(onfulfill);
+  }
+  if (isCallback(onreject)) {
+    scope._addRejectCallback(onreject);
+  }
+  return scope;
+};
+
+//
+// Prototype properties
+//
+
+Promise.prototype = Object.create(null, {
+  "then": _public(function(onfulfill, onreject) {
+    // The logic here is:
+    //    We return a new Promise whose resolution merges with the return from
+    //    onfulfill() or onerror(). If onfulfill() returns a Promise, we forward
+    //    the resolution of that future to the resolution of the returned
+    //    Promise.
+    var f = this;
+    return new Promise(function(r) {
+      addCallbacks(wrap(onfulfill, r, "resolve"),
+                   wrap(onreject, r, "reject"), f);
+    });
+  }),
+  "catch": _public(function(onreject) {
+    var f = this;
+    return new Promise(function(r) {
+      addCallbacks(null, wrap(onreject, r, "reject"), f);
+    });
+  }),
+});
+
+//
+// Statics
+//
+
+Promise.isThenable = isThenable;
+
+var toPromiseList = function(list) {
+  return Array.prototype.slice.call(list).map(Promise.resolve);
+};
+
+Promise.any = function(/*...futuresOrValues*/) {
+  var futures = toPromiseList(arguments);
+  return new Promise(function(r) {
+    if (!futures.length) {
+      r.reject("No futures passed to Promise.any()");
+    } else {
+      var resolved = false;
+      var firstSuccess = function(value) {
+        if (resolved) { return; }
+        resolved = true;
+        r.resolve(value);
+      };
+      var firstFailure = function(reason) {
+        if (resolved) { return; }
+        resolved = true;
+        r.reject(reason);
+      };
+      futures.forEach(function(f, idx) {
+        f.then(firstSuccess, firstFailure);
+      });
+    }
+  });
+};
+
+Promise.every = function(/*...futuresOrValues*/) {
+  var futures = toPromiseList(arguments);
+  return new Promise(function(r) {
+    if (!futures.length) {
+      r.reject("No futures passed to Promise.every()");
+    } else {
+      var values = new Array(futures.length);
+      var count = 0;
+      var accumulate = function(idx, v) {
+        count++;
+        values[idx] = v;
+        if (count == futures.length) {
+          r.resolve(values);
+        }
+      };
+      futures.forEach(function(f, idx) {
+        f.then(accumulate.bind(null, idx), r.reject);
+      });
+    }
+  });
+};
+
+Promise.some = function() {
+  var futures = toPromiseList(arguments);
+  return new Promise(function(r) {
+    if (!futures.length) {
+      r.reject("No futures passed to Promise.some()");
+    } else {
+      var count = 0;
+      var accumulateFailures = function(e) {
+        count++;
+        if (count == futures.length) {
+          r.reject();
+        }
+      };
+      futures.forEach(function(f, idx) {
+        f.then(r.resolve, accumulateFailures);
+      });
+    }
+  });
+};
+
+Promise.fulfill = function(value) {
+  return new Promise(function(r) {
+    r.fulfill(value);
+  });
+};
+
+Promise.resolve = function(value) {
+  return new Promise(function(r) {
+    r.resolve(value);
+  });
+};
+
+Promise.reject = function(reason) {
+  return new Promise(function(r) {
+    r.reject(reason);
+  });
+};
+
+//
+// Export
+//
+
+global.Promise = Promise;
+
+})(this,
+  (typeof window !== 'undefined') ? window : {},
+  this.runningUnderTest||false);
--- a/playground/index.html	Mon Jul 08 14:09:59 2013 -0400
+++ b/playground/index.html	Mon Jul 08 14:51:22 2013 -0400
@@ -29,7 +29,7 @@
       <script type="text/javascript" src="../common/prettify.js"></script>
       <script type="text/javascript" src="../common/lang-jsonld.js"></script>
       <script type="text/javascript" src="../common/lang-nquads.js"></script>
-      <script type="text/javascript" src="Future.js"></script>
+      <script type="text/javascript" src="Promise.js"></script>
       <script type="text/javascript" src="jsonld.js"></script>
       <script type="text/javascript" src="playground.js"></script>
       <script type="text/javascript" src="playground-examples.js"></script>
--- a/playground/jsonld.js	Mon Jul 08 14:09:59 2013 -0400
+++ b/playground/jsonld.js	Mon Jul 08 14:51:22 2013 -0400
@@ -817,16 +817,16 @@
     'implemented.', 'jsonld.DocumentUrlError'), url);
 };
 
-/* Futures/Promises API */
-
-jsonld.futures = jsonld.promises = function() {
-  var Future = _nodejs ? require('./Future') : global.Future;
+/* Promises API */
+
+jsonld.promises = function() {
+  var Promise = _nodejs ? require('./Promise').Promise : global.Promise;
   var slice = Array.prototype.slice;
 
-  // converts a node.js async op into a future w/boxed resolved value(s)
-  function futurize(op) {
+  // converts a node.js async op into a promise w/boxed resolved value(s)
+  function promisify(op) {
     var args = slice.call(arguments, 1);
-    return new Future(function(resolver) {
+    return new Promise(function(resolver) {
       op.apply(null, args.concat(function(err, value) {
         if(err) {
           resolver.reject(err);
@@ -861,7 +861,7 @@
     if('loadDocument' in options) {
       options.loadDocument = createDocumentLoader(options.loadDocument);
     }
-    return futurize.apply(null, [jsonld.expand].concat(slice.call(arguments)));
+    return promisify.apply(null, [jsonld.expand].concat(slice.call(arguments)));
   };
   api.compact = function(input, ctx) {
     if(arguments.length < 2) {
@@ -877,7 +877,7 @@
         callback(err, compacted);
       });
     };
-    return futurize.apply(null, [compact].concat(slice.call(arguments)));
+    return promisify.apply(null, [compact].concat(slice.call(arguments)));
   };
   api.flatten = function(input) {
     if(arguments.length < 1) {
@@ -887,7 +887,8 @@
     if('loadDocument' in options) {
       options.loadDocument = createDocumentLoader(options.loadDocument);
     }
-    return futurize.apply(null, [jsonld.flatten].concat(slice.call(arguments)));
+    return promisify.apply(
+      null, [jsonld.flatten].concat(slice.call(arguments)));
   };
   api.frame = function(input, frame) {
     if(arguments.length < 2) {
@@ -897,13 +898,14 @@
     if('loadDocument' in options) {
       options.loadDocument = createDocumentLoader(options.loadDocument);
     }
-    return futurize.apply(null, [jsonld.frame].concat(slice.call(arguments)));
+    return promisify.apply(null, [jsonld.frame].concat(slice.call(arguments)));
   };
   api.fromRDF = function(dataset) {
     if(arguments.length < 1) {
       throw new TypeError('Could not convert from RDF, too few arguments.');
     }
-    return futurize.apply(null, [jsonld.fromRDF].concat(slice.call(arguments)));
+    return promisify.apply(
+      null, [jsonld.fromRDF].concat(slice.call(arguments)));
   };
   api.toRDF = function(input) {
     if(arguments.length < 1) {
@@ -913,7 +915,7 @@
     if('loadDocument' in options) {
       options.loadDocument = createDocumentLoader(options.loadDocument);
     }
-    return futurize.apply(null, [jsonld.toRDF].concat(slice.call(arguments)));
+    return promisify.apply(null, [jsonld.toRDF].concat(slice.call(arguments)));
   };
   api.normalize = function(input) {
     if(arguments.length < 1) {
@@ -923,7 +925,7 @@
     if('loadDocument' in options) {
       options.loadDocument = createDocumentLoader(options.loadDocument);
     }
-    return futurize.apply(
+    return promisify.apply(
       null, [jsonld.normalize].concat(slice.call(arguments)));
   };
   return api;
@@ -932,7 +934,7 @@
 /* WebIDL API */
 
 function JsonLdProcessor() {}
-JsonLdProcessor.prototype = jsonld.futures();
+JsonLdProcessor.prototype = jsonld.promises();
 JsonLdProcessor.prototype.toString = function() {
   if(this instanceof JsonLdProcessor) {
     return '[object JsonLdProcessor]';
--- a/playground/playground.js	Mon Jul 08 14:09:59 2013 -0400
+++ b/playground/playground.js	Mon Jul 08 14:51:22 2013 -0400
@@ -157,7 +157,6 @@
    * @param ui the ui tab object that was selected
    */
   playground.tabSelected = function(event, ui) {
-    console.log('tab selected');
     playground.activeTab = ui.tab.id;
     if(ui.tab.id === 'tab-compacted' || ui.tab.id === 'tab-flattened' ||
       ui.tab.id === 'tab-framed') {
@@ -191,21 +190,21 @@
   };
 
   /**
-   * Returns a Future to performs the JSON-LD API action based on the active
+   * Returns a Promise to performs the JSON-LD API action based on the active
    * tab.
    *
    * @param input the JSON-LD object input or null no error.
    * @param param the JSON-LD param to use.
    */
   playground.performAction = function(input, param) {
-    return new Future(function(resolver) {
+    return new Promise(function(resolver) {
       var processor = new jsonld.JsonLdProcessor();
 
       // set base IRI
       var options = {base: document.baseURI};
 
       if(playground.activeTab === 'tab-compacted') {
-        processor.compact(input, param, options).done(function(compacted) {
+        processor.compact(input, param, options).then(function(compacted) {
           $('#compacted').html(js_beautify(
             playground.htmlEscape(JSON.stringify(compacted)),
             {'indent_size': 2}).replace(/\n/g, '<br>'));
@@ -213,7 +212,7 @@
         }, resolver.reject);
       }
       else if(playground.activeTab === 'tab-expanded') {
-        processor.expand(input, options).done(function(expanded) {
+        processor.expand(input, options).then(function(expanded) {
           $('#expanded').html(js_beautify(
             playground.htmlEscape(JSON.stringify(expanded)),
             {'indent_size': 2}).replace(/\n/g, '<br>'));
@@ -221,7 +220,7 @@
         }, resolver.reject);
       }
       else if(playground.activeTab === 'tab-flattened') {
-        processor.flatten(input, param, options).done(function(flattened) {
+        processor.flatten(input, param, options).then(function(flattened) {
           $('#flattened').html(js_beautify(
             playground.htmlEscape(JSON.stringify(flattened)),
             {'indent_size': 2}).replace(/\n/g, '<br>'));
@@ -229,7 +228,7 @@
         }, resolver.reject);
       }
       else if(playground.activeTab === 'tab-framed') {
-        processor.frame(input, param, options).done(function(framed) {
+        processor.frame(input, param, options).then(function(framed) {
           $('#framed').html(js_beautify(
             playground.htmlEscape(JSON.stringify(framed)),
             {'indent_size': 2}).replace(/\n/g, '<br>'));
@@ -238,7 +237,7 @@
       }
       else if(playground.activeTab === 'tab-nquads') {
         options.format = 'application/nquads';
-        processor.toRDF(input, options).done(function(nquads) {
+        processor.toRDF(input, options).then(function(nquads) {
           $('#nquads').html(
             playground.htmlEscape(nquads).replace(/\n/g, '<br>'));
           resolver.resolve();
@@ -246,7 +245,7 @@
       }
       else if(playground.activeTab === 'tab-normalized') {
         options.format = 'application/nquads';
-        processor.normalize(input, options).done(function(normalized) {
+        processor.normalize(input, options).then(function(normalized) {
           $('#normalized').html(
             playground.htmlEscape(normalized).replace(/\n/g, '<br>'));
           resolver.resolve();
@@ -315,7 +314,7 @@
     }
 
     // no errors, perform the action and display the output
-    playground.performAction(input, param).done(function() {
+    playground.performAction(input, param).then(function() {
       // generate a link for current data
       var link = '?json-ld=' + encodeURIComponent(JSON.stringify(input));
       if($('#frame').val().length > 0) {