diff --git a/html/fs/index.html b/html/fs/index.html
index 7138bf08..79b1914c 100644
--- a/html/fs/index.html
+++ b/html/fs/index.html
@@ -42,6 +42,7 @@
client = 'client/',
files = [
'util',
+ 'promise',
'cloudfunc',
client + 'dom',
client + 'events',
diff --git a/lib/promise.js b/lib/promise.js
new file mode 100644
index 00000000..a095b193
--- /dev/null
+++ b/lib/promise.js
@@ -0,0 +1,183 @@
+/*
+ * version: 1.1.2
+ * url: https://github.com/taylorhakes/promise-polyfill
+ */
+
+(function(global) {
+ if(typeof module !== 'undefined' && module.exports) {
+ module.exports = global.Promise ? global.Promise : Promise;
+ } else if (!global.Promise) {
+ global.Promise = Promise;
+ }
+
+ // Use polyfill for setImmediate for performance gains
+ var asap = global.setImmediate || function(fn) { setTimeout(fn, 1); };
+
+ // Polyfill for Function.prototype.bind
+ function bind(fn, thisArg) {
+ return function() {
+ fn.apply(thisArg, arguments);
+ }
+ }
+
+ var isArray = Array.isArray || function(value) { return Object.prototype.toString.call(value) === "[object Array]" };
+
+ function Promise(fn) {
+ if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
+ if (typeof fn !== 'function') throw new TypeError('not a function');
+ this._state = null;
+ this._value = null;
+ this._deferreds = []
+
+ doResolve(fn, bind(resolve, this), bind(reject, this))
+ }
+
+ function handle(deferred) {
+ var me = this;
+ if (this._state === null) {
+ this._deferreds.push(deferred);
+ return
+ }
+ asap(function() {
+ var cb = me._state ? deferred.onFulfilled : deferred.onRejected
+ if (cb === null) {
+ (me._state ? deferred.resolve : deferred.reject)(me._value);
+ return;
+ }
+ var ret;
+ try {
+ ret = cb(me._value);
+ }
+ catch (e) {
+ deferred.reject(e);
+ return;
+ }
+ deferred.resolve(ret);
+ })
+ }
+
+ function resolve(newValue) {
+ try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+ if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.');
+ if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
+ var then = newValue.then;
+ if (typeof then === 'function') {
+ doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this));
+ return;
+ }
+ }
+ this._state = true;
+ this._value = newValue;
+ finale.call(this);
+ } catch (e) { reject.call(this, e); }
+ }
+
+ function reject(newValue) {
+ this._state = false;
+ this._value = newValue;
+ finale.call(this);
+ }
+
+ function finale() {
+ for (var i = 0, len = this._deferreds.length; i < len; i++) {
+ handle.call(this, this._deferreds[i]);
+ }
+ this._deferreds = null;
+ }
+
+ function Handler(onFulfilled, onRejected, resolve, reject){
+ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+ this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+ this.resolve = resolve;
+ this.reject = reject;
+ }
+
+ /**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+ function doResolve(fn, onFulfilled, onRejected) {
+ var done = false;
+ try {
+ fn(function (value) {
+ if (done) return;
+ done = true;
+ onFulfilled(value);
+ }, function (reason) {
+ if (done) return;
+ done = true;
+ onRejected(reason);
+ })
+ } catch (ex) {
+ if (done) return;
+ done = true;
+ onRejected(ex);
+ }
+ }
+
+ Promise.prototype['catch'] = function (onRejected) {
+ return this.then(null, onRejected);
+ };
+
+ Promise.prototype.then = function(onFulfilled, onRejected) {
+ var me = this;
+ return new Promise(function(resolve, reject) {
+ handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject));
+ })
+ };
+
+ Promise.all = function () {
+ var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments);
+
+ return new Promise(function (resolve, reject) {
+ if (args.length === 0) return resolve([]);
+ var remaining = args.length;
+ function res(i, val) {
+ try {
+ if (val && (typeof val === 'object' || typeof val === 'function')) {
+ var then = val.then;
+ if (typeof then === 'function') {
+ then.call(val, function (val) { res(i, val) }, reject);
+ return;
+ }
+ }
+ args[i] = val;
+ if (--remaining === 0) {
+ resolve(args);
+ }
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+ for (var i = 0; i < args.length; i++) {
+ res(i, args[i]);
+ }
+ });
+ };
+
+ Promise.resolve = function (value) {
+ if (value && typeof value === 'object' && value.constructor === Promise) {
+ return value;
+ }
+
+ return new Promise(function (resolve) {
+ resolve(value);
+ });
+ };
+
+ Promise.reject = function (value) {
+ return new Promise(function (resolve, reject) {
+ reject(value);
+ });
+ };
+
+ Promise.race = function (values) {
+ return new Promise(function (resolve, reject) {
+ for(var i = 0, len = values.length; i < len; i++) {
+ values[i].then(resolve, reject);
+ }
+ });
+ };
+})(this);
\ No newline at end of file