Commit 3e152964 authored by Tristan Cavelier's avatar Tristan Cavelier

promy replaced by RSVP library

parent 78fe0cd7
......@@ -14,12 +14,6 @@ module.exports = function (grunt) {
errorsOnly: true
}
},
promy: {
src: ['src/promy/**/*.js'],
options: {
errorsOnly: true
}
},
jio: {
src: ['src/jio/**/*.js'],
exclude: ['src/jio/intro.js', 'src/jio/outro.js'],
......@@ -84,10 +78,6 @@ module.exports = function (grunt) {
banner: '/*! <%= pkg.name %> <%= pkg.version %> ' +
'<%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
promy: {
src: 'src/promy/promy.js',
dest: 'promy.min.js'
},
jio: {
src: 'jio.js', // '<%= pkg.name %>.js'
dest: 'jio.min.js'
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global constants, dictUpdate, deepClone */
function restCommandRejecter(param, args) {
// reject(status, reason, message, {"custom": "value"});
// reject(status, reason, {..});
// reject(status, {..});
var a = args[0], b = args[1], c = args[2], d = args[3], weak, strong;
weak = {"result": "error"};
strong = {};
weak.status = constants.http_status.unknown;
weak.statusText = constants.http_status_text.unknown;
weak.message = 'Command failed';
weak.reason = 'fail';
weak.method = param.method;
if (param.kwargs._id) {
weak.id = param.kwargs._id;
}
if (/Attachment$/.test(param.method)) {
weak.attachment = param.kwargs._attachment;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return restCommandRejecter(param, [
// can create infernal loop if 'internal_storage_error' is not defined
// in the constants
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
]);
}
a = b;
b = c;
c = d;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.reason = a;
a = b;
b = c;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.message = a;
a = b;
}
if (typeof a === 'object' && !Array.isArray(a)) {
dictUpdate(weak, a);
}
dictUpdate(weak, strong);
strong = undefined;
if (weak.error === undefined) {
weak.error = weak.statusText.toLowerCase().replace(/ /g, '_').
replace(/[^_a-z]/g, '');
}
if (typeof weak.message !== 'string') {
weak.message = "";
}
if (typeof weak.reason !== 'string') {
weak.reason = "unknown";
}
return param.solver.reject(deepClone(weak));
}
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true, regexp: true */
/*global Deferred, inherits, constants, dictUpdate, deepClone, Blob,
methodType */
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global constants, methodType, dictUpdate, Blob, deepClone,
restCommandRejecter */
function IODeferred(method, info) {
IODeferred.super_.call(this);
this._info = info || {};
this._method = method;
// this._options = options;
}
inherits(IODeferred, Deferred);
IODeferred.prototype.resolve = function (a, b) {
function restCommandResolver(param, args) {
// resolve('ok', {"custom": "value"});
// resolve(200, {...});
// resolve({...});
var weak = {"result": "success"}, strong = {};
if (this._method === 'post') {
var a = args[0], b = args[1], weak = {"result": "success"}, strong = {};
if (param.method === 'post') {
weak.status = constants.http_status.created;
weak.statusText = constants.http_status_text.created;
} else if (methodType(this._method) === "writer" ||
this._method === "check") {
} else if (methodType(param.method) === "writer" ||
param.method === "check") {
weak.status = constants.http_status.no_content;
weak.statusText = constants.http_status_text.no_content;
} else {
weak.status = constants.http_status.ok;
weak.statusText = constants.http_status_text.ok;
}
if (this._info._id) {
weak.id = this._info._id;
if (param.kwargs._id) {
weak.id = param.kwargs._id;
}
if (/Attachment$/.test(this._method)) {
weak.attachment = this._info._attachment;
if (/Attachment$/.test(param.method)) {
weak.attachment = param.kwargs._attachment;
}
weak.method = this._method;
weak.method = param.method;
if (typeof a === 'string' || (typeof a === 'number' && isFinite(a))) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return this.reject(
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
);
]);
}
a = b;
}
......@@ -52,14 +44,14 @@ IODeferred.prototype.resolve = function (a, b) {
}
dictUpdate(weak, strong);
strong = undefined; // free memory
if (this._method === 'post' && (typeof weak.id !== 'string' || !weak.id)) {
return this.reject(
if (param.method === 'post' && (typeof weak.id !== 'string' || !weak.id)) {
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
'New document id have to be specified'
);
]);
}
if (this._method === 'getAttachment') {
if (param.method === 'getAttachment') {
if (typeof weak.data === 'string') {
weak.data = new Blob([weak.data], {
"type": weak.content_type || weak.mimetype || ""
......@@ -68,111 +60,21 @@ IODeferred.prototype.resolve = function (a, b) {
delete weak.mimetype;
}
if (!(weak.data instanceof Blob)) {
return this.reject(
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
'getAttachment method needs a Blob as returned "data".'
);
]);
}
} else if (methodType(this._method) === 'reader' &&
this._method !== 'check' &&
} else if (methodType(param.method) === 'reader' &&
param.method !== 'check' &&
(typeof weak.data !== 'object' ||
Object.getPrototypeOf(weak.data) !== Object.prototype)) {
return this.reject(
return restCommandRejecter(param, [
'internal_storage_error',
'invalid response',
this._method + ' method needs a dict as returned "data".'
);
}
//return super_resolve(deepClone(weak));
return IODeferred.super_.prototype.resolve.call(this, deepClone(weak));
};
IODeferred.prototype.reject = function (a, b, c, d) {
// reject(status, reason, message, {"custom": "value"});
// reject(status, reason, {..});
// reject(status, {..});
var weak = {"result": "error"}, strong = {};
weak.status = constants.http_status.unknown;
weak.statusText = constants.http_status_text.unknown;
weak.message = 'Command failed';
weak.reason = 'fail';
weak.method = this._method;
if (this._info._id) {
weak.id = this._info._id;
}
if (/Attachment$/.test(this._method)) {
weak.attachment = this._info._attachment;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.status = constants.http_status[a];
strong.statusText = constants.http_status_text[a];
if (strong.status === undefined ||
strong.statusText === undefined) {
return this.reject(
// can create infernal loop if 'internal_storage_error' is not defined
// in the constants
'internal_storage_error',
'invalid response',
'Unknown status "' + a + '"'
);
}
a = b;
b = c;
c = d;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.reason = a;
a = b;
b = c;
}
if (typeof a !== 'object' || Array.isArray(a)) {
strong.message = a;
a = b;
}
if (typeof a === 'object' && !Array.isArray(a)) {
dictUpdate(weak, a);
param.method + ' method needs a dict as returned "data".'
]);
}
dictUpdate(weak, strong);
strong = undefined;
if (weak.error === undefined) {
weak.error = weak.statusText.toLowerCase().replace(/ /g, '_').
replace(/[^_a-z]/g, '');
}
if (typeof weak.message !== 'string') {
weak.message = "";
}
if (typeof weak.reason !== 'string') {
weak.reason = "unknown";
}
//return super_reject(deepClone(weak));
return IODeferred.super_.prototype.reject.call(this, deepClone(weak));
};
IODeferred.createFromDeferred = function (method, info, options, deferred) {
var iodeferred = new IODeferred(method, info, options);
// iodeferred.promise().done(deferred.resolve.bind(deferred)).
// fail(deferred.reject.bind(deferred)).
// progress(deferred.notify.bind(deferred));
// // phantomjs doesn't like 'bind'...
iodeferred.promise.then(
deferred.resolve.bind(deferred),
deferred.reject.bind(deferred),
deferred.notify.bind(deferred)
);
return iodeferred;
};
IODeferred.createFromParam = function (param) {
return IODeferred.createFromDeferred(
param.method,
param.kwargs,
param.options,
param.deferred
);
};
return param.solver.resolve(deepClone(weak));
}
/*jslint indent: 2, maxlen: 80, nomen: true, sloppy: true */
/*global exports, Blob, FileReader, Deferred, hex_sha256, XMLHttpRequest,
/*global exports, Blob, FileReader, RSVP, hex_sha256, XMLHttpRequest,
constants */
/**
......@@ -310,32 +310,41 @@ function makeBinaryStringDigest(string) {
exports.util.makeBinaryStringDigest = makeBinaryStringDigest;
function readBlobAsBinaryString(blob) {
var deferred = new Deferred(), fr = new FileReader();
fr.addEventListener("load", deferred.resolve.bind(deferred));
fr.addEventListener("error", deferred.reject.bind(deferred));
fr.addEventListener("progress", deferred.notify.bind(deferred));
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsBinaryString(blob);
return deferred.promise;
}, function () {
fr.abort();
});
}
exports.util.readBlobAsBinaryString = readBlobAsBinaryString;
function readBlobAsArrayBuffer(blob) {
var deferred = new Deferred(), fr = new FileReader();
fr.addEventListener("load", deferred.resolve.bind(deferred));
fr.addEventListener("error", deferred.reject.bind(deferred));
fr.addEventListener("progress", deferred.notify.bind(deferred));
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsArrayBuffer(blob);
return deferred.promise;
}, function () {
fr.abort();
});
}
exports.util.readBlobAsArrayBuffer = readBlobAsArrayBuffer;
function readBlobAsText(blob) {
var deferred = new Deferred(), fr = new FileReader();
fr.addEventListener("load", deferred.resolve.bind(deferred));
fr.addEventListener("error", deferred.reject.bind(deferred));
fr.addEventListener("progress", deferred.notify.bind(deferred));
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
fr.addEventListener("load", resolve);
fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsText(blob);
return deferred.promise;
}, function () {
fr.abort();
});
}
exports.util.readBlobAsText = readBlobAsText;
......@@ -355,7 +364,9 @@ exports.util.readBlobAsText = readBlobAsText;
* @return {Promise} The promise
*/
function ajax(param) {
var k, xhr = new XMLHttpRequest(), deferred = new Deferred();
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.responseType = param.dataType || "";
if (typeof param.headers === 'object' && param.headers !== null) {
......@@ -367,17 +378,19 @@ function ajax(param) {
}
xhr.addEventListener("load", function (e) {
if (e.target.status >= 400) {
return deferred.reject(e);
return reject(e);
}
deferred.resolve(e);
resolve(e);
});
xhr.addEventListener("error", deferred.reject.bind(deferred));
xhr.addEventListener("progress", deferred.notify.bind(deferred));
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.beforeSend === 'function') {
param.beforeSend(xhr);
}
xhr.send(param.data);
return deferred.promise;
}, function () {
xhr.abort();
});
}
exports.util.ajax = ajax;
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global arrayInsert, indexOf, deepClone, defaults, IODeferred */
/*global arrayInsert, indexOf, deepClone, defaults, restCommandRejecter */
// creates
// - some defaults job rule actions
......@@ -8,11 +8,12 @@ function enableJobChecker(jio, shared, options) {
// dependencies
// - shared.jobs Object Array
// - param.promise Object
// creates
// - shared.job_rules Array
// uses 'job' events
// uses 'job' event
var i;
......@@ -20,24 +21,25 @@ function enableJobChecker(jio, shared, options) {
shared.job_rule_actions = {
wait: function (original_job, new_job) {
original_job.deferred.promise.always(function () {
original_job.promise.always(function () {
shared.emit('job', new_job);
});
new_job.state = 'waiting';
new_job.modified = new Date();
},
update: function (original_job, new_job) {
if (!new_job.deferred) {
if (!new_job.solver) {
// promise associated to the job
new_job.state = 'done';
shared.emit('jobDone', new_job);
} else {
if (!original_job.deferred) {
original_job.deferred = new_job.deferred;
if (!original_job.solver) {
original_job.solver = new_job.solver;
} else {
original_job.deferred.promise.
done(new_job.command.resolve).
fail(new_job.command.reject);
original_job.promise.then(
new_job.command.resolve,
new_job.command.reject
);
}
}
new_job.state = 'running';
......@@ -46,11 +48,11 @@ function enableJobChecker(jio, shared, options) {
deny: function (original_job, new_job) {
new_job.state = 'fail';
new_job.modified = new Date();
IODeferred.createFromParam(new_job).reject(
restCommandRejecter(new_job, [
'precondition_failed',
'command denied',
'Command rejected by the job checker.'
);
]);
}
};
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global setTimeout, Job, createStorage, deepClone, IODeferred, min */
/*global setTimeout, Job, createStorage, deepClone, min, restCommandResolver,
restCommandRejecter */
function enableJobExecuter(jio, shared) { // , options) {
......@@ -46,44 +47,30 @@ function enableJobExecuter(jio, shared) { // , options) {
});
shared.on('jobDone', function (param, args) {
var d;
if (param.state === 'running') {
param.state = 'done';
param.modified = new Date();
shared.emit('jobEnd', param);
if (param.deferred) {
d = IODeferred.createFromDeferred(
param.method,
param.kwargs,
param.options,
param.deferred
);
d.resolve.apply(d, args);
if (param.solver) {
restCommandResolver(param, args);
}
}
});
shared.on('jobFail', function (param, args) {
var d;
if (param.state === 'running') {
param.state = 'fail';
param.modified = new Date();
shared.emit('jobEnd', param);
if (param.deferred) {
d = IODeferred.createFromDeferred(
param.method,
param.kwargs,
param.options,
param.deferred
);
d.reject.apply(d, args);
if (param.solver) {
restCommandRejecter(param, args);
}
}
});
shared.on('jobNotify', function (param, args) {
if (param.state === 'running' && param.deferred) {
param.deferred.notify.apply(param.deferred, args);
if (param.state === 'running' && param.solver) {
param.solver.notify(args[0]);
}
});
}
......@@ -58,7 +58,7 @@ function enableJobMaker(jio, shared, options) {
shared.rest_method_names.forEach(function (method) {
shared.on(method, function (param) {
if (param.deferred) {
if (param.solver) {
// params are good
shared.emit('job', param);
}
......
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global arrayValuesToTypeDict, dictClear, Deferred, deepClone */
/*global arrayValuesToTypeDict, dictClear, RSVP, deepClone */
// adds methods to JIO
// - post
......@@ -18,7 +18,12 @@
// - method string
// - kwargs object
// - options object
// - command object
// - solver object
// - solver.resolve function
// - solver.reject function
// - solver.notify function
// - cancellers object
// - promise object
function enableRestAPI(jio, shared) { // (jio, shared, options)
......@@ -36,7 +41,7 @@ function enableRestAPI(jio, shared) { // (jio, shared, options)
];
function prepareParamAndEmit(method, storage_spec, args) {
var promise, callback, type_dict, param = {};
var callback, type_dict, param = {};
type_dict = arrayValuesToTypeDict(Array.prototype.slice.call(args));
type_dict.object = type_dict.object || [];
if (method !== 'allDocs') {
......@@ -49,31 +54,38 @@ function enableRestAPI(jio, shared) { // (jio, shared, options)
} else {
param.kwargs = {};
}
param.solver = {};
param.options = deepClone(type_dict.object.shift()) || {};
//param.deferred = new IODeferred(method, param.kwargs, param.options);
param.deferred = new Deferred();
promise = param.deferred.promise;
param.promise = new RSVP.Promise(function (resolve, reject, notify) {
param.solver.resolve = resolve;
param.solver.reject = reject;
param.solver.notify = notify;
}, function () {
var k;
for (k in param.cancellers) {
if (param.cancellers.hasOwnProperty(k)) {
param.cancellers[k]();
}
}
});
type_dict['function'] = type_dict['function'] || [];
if (type_dict['function'].length === 1) {
callback = type_dict['function'].shift();
promise.done(function (answer) {
callback = type_dict['function'][0];
param.promise.then(function (answer) {
callback(undefined, answer);
});
promise.fail(function (answer) {
}, function (answer) {
callback(answer, undefined);
});
} else if (type_dict['function'].length > 1) {
promise.done(type_dict['function'].shift());
promise.fail(type_dict['function'].shift());
if (type_dict['function'].length === 1) {
promise.always(type_dict['function'].shift());
}
param.promise.then(type_dict['function'][0],
type_dict['function'][1],
type_dict['function'][2]);
}
type_dict = dictClear(type_dict);
param.storage_spec = storage_spec;
param.method = method;
shared.emit(method, param);
return promise;
return param.promise;
}
shared.createRestApi = function (storage_spec, that) {
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, unparam: true */
/*global Blob, IODeferred, Metadata */
/*global Blob, restCommandRejecter, Metadata */
function enableRestParamChecker(jio, shared) {
// dependencies
// - param.deferred
// - param.solver
// - param.kwargs
// checks the kwargs and convert value if necessary
......@@ -17,12 +17,12 @@ function enableRestParamChecker(jio, shared) {
function checkId(param) {
if (typeof param.kwargs._id !== 'string' || param.kwargs._id === '') {
IODeferred.createFromParam(param).reject(
restCommandRejecter(param, [
'bad_request',
'wrong document id',
'Document id must be a non empty string.'
);
delete param.deferred;
]);
delete param.solver;
return false;
}
return true;
......@@ -31,12 +31,12 @@ function enableRestParamChecker(jio, shared) {
function checkAttachmentId(param) {
if (typeof param.kwargs._attachment !== 'string' ||
param.kwargs._attachment === '') {
IODeferred.createFromParam(param).reject(
restCommandRejecter(param, [
'bad_request',
'wrong attachment id',
'Attachment id must be a non empty string.'
);
delete param.deferred;
]);
delete param.solver;
return false;
}
return true;
......@@ -84,15 +84,15 @@ function enableRestParamChecker(jio, shared) {
delete param.kwargs._mimetype;
delete param.kwargs._content_type;
} else {
IODeferred.createFromParam(param).reject(
restCommandRejecter(param, [
'bad_request',
'wrong attachment',
'Attachment information must be like {"_id": document id, ' +
'"_attachment": attachment name, "_data": string, ["_mimetype": ' +
'content type]} or {"_id": document id, "_attachment": ' +
'attachment name, "_blob": Blob}'
);
delete param.deferred;
]);
delete param.solver;
}
});
......
......@@ -4,11 +4,11 @@
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(exports, require('promy'), require('sha256'));
return module(exports, require('rsvp'), require('sha256'));
}
window.jIO = {};
module(window.jIO, promy, {hex_sha256: hex_sha256});
}(['exports', 'promy', 'sha256'], function (exports, promy, sha256) {
module(window.jIO, RSVP, {hex_sha256: hex_sha256});
}(['exports', 'rsvp', 'sha256'], function (exports, RSVP, sha256) {
"use strict";
var hex_sha256 = sha256.hex_sha256, Deferred = promy.Deferred;
var hex_sha256 = sha256.hex_sha256;
/*
* Promy: Promises library
* Copyright (C) 2013 Nexedi SA
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global setInterval, setTimeout, clearInterval, clearTimeout */
(function (dependencies, module) {
"use strict";
/*global define, exports, window */
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
module(exports);
}
if (typeof window === 'object') {
window.promy = {};
module(window.promy);
}
}(['exports'], function (exports) {
"use strict";
var UNRESOLVED = 0, RESOLVED = 1, REJECTED = 2, CANCELLED = 3;
/**
* thenItem(item, [onSucess], [onError], [onProgress]): any
*
* Execute one of the given callback when the item is fulfilled. If the item
* is not a promise, then onSuccess is called with the item as first
* parameter.
*
* @param {Any} item A promise, deferred or a simple value
* @param {Function} [onSuccess] The callback to call on resolve
* @param {Function} [onError] The callback to call on reject
* @param {Function} [onProgress] The callback to call on notify
*/
function thenItem(item, onSuccess, onError, onProgress) {
if (typeof item === 'object' && item !== null) {
if (typeof item.promise === 'object' && item.promise !== null &&
typeof item.promise.then === 'function') {
// item seams to be a Deferred
return item.promise.then(
onSuccess,
onError,
onProgress
);
}
if (typeof item.then === 'function') {
// item seams to be a Promise
return item.then(
onSuccess,
onError,
onProgress
);
}
}
return onSuccess(item);
}
/**
* promiseResolve(promise, answers): any
*
* Resolve the promise with the given answers.
*
* @param {Promise} promise The promise to resolve
* @param {Array} answers The arguments to give
*/
function promiseResolve(promise, answers) {
var array;
if (promise._state === UNRESOLVED) {
promise._state = RESOLVED;
promise._answers = answers;
array = promise._onResolve.slice();
setTimeout(function () {
var i;
for (i = 0; i < array.length; i += 1) {
try {
array[i].apply(promise, promise._answers);
} catch (ignore) {} // errors will never be retrieved by global
}
});
// free the memory
promise._onResolve = undefined;
promise._onReject = undefined;
promise._onProgress = undefined;
}
}
/**
* promiseReject(promise, answers): any
*
* Reject the promise with the given answers.
*
* @param {Promise} promise The promise to reject
* @param {Array} answers The arguments to give
*/
function promiseReject(promise, answers) {
var array;
if (promise._state === UNRESOLVED) {
promise._state = REJECTED;
promise._answers = answers;
array = promise._onReject.slice();
setTimeout(function () {
var i;
for (i = 0; i < array.length; i += 1) {
try {
array[i].apply(promise, promise._answers);
} catch (ignore) {} // errors will never be retrieved by global
}
});
// free the memory
promise._onResolve = undefined;
promise._onReject = undefined;
promise._onProgress = undefined;
}
}
/**
* promiseNotify(promise, answers): any
*
* Notify the promise with the given answers.
*
* @param {Promise} promise The promise to notify
* @param {Array} answers The arguments to give
*/
function promiseNotify(promise, answers) {
var i;
if (promise._onProgress) {
for (i = 0; i < promise._onProgress.length; i += 1) {
try {
promise._onProgress[i].apply(promise, answers);
} catch (ignore) {} // errors will never be retrieved by global
}
}
}
/**
* Promise(cancel)
*
* @class Promise
* @constructor
*/
function Promise(cancel) {
this._onReject = [];
this._onResolve = [];
this._onProgress = [];
this._state = UNRESOLVED;
if (typeof cancel === 'function') {
this._cancel = cancel;
}
}
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/A
// then(fulfilledHandler, errorHandler, progressHandler)
/**
* then([onSuccess], [onError], [onProgress]): Promise
*
* Returns a new Promise with the return value of the `onSuccess` or `onError`
* callback as first parameter. If the pervious promise is resolved, the
* `onSuccess` callback is called. If rejected, the `onError` callback is
* called. If notified, `onProgress` is called.
*
* Deferred.when(1).
* then(function (one) { return one + 1; }).
* then(console.log); // shows 2
*
* @method then
* @param {Function} [onSuccess] The callback to call on resolve
* @param {Function} [onError] The callback to call on reject
* @param {Function} [onProgress] The callback to call on notify
* @return {Promise} The new promise
*/
Promise.prototype.then = function (onSuccess, onError, onProgress) {
/*global Deferred*/
var defer, next = new this.constructor(this._cancel), that = this;
defer = new Deferred();
defer.promise = next;
switch (this._state) {
case RESOLVED:
if (typeof onSuccess === 'function') {
setTimeout(function () {
try {
thenItem(
onSuccess.apply(that, that._answers),
defer.resolve.bind(defer),
defer.reject.bind(defer)
);
} catch (e) {
defer.reject(e);
}
});
} else {
setTimeout(function () {
defer.resolve.apply(defer, that._answers);
});
}
break;
case REJECTED:
if (typeof onError === 'function') {
setTimeout(function () {
var result;
try {
result = onError.apply(that, that._answers);
if (result === undefined) {
return defer.reject.apply(defer, that._answers);
}
thenItem(
result,
defer.reject.bind(defer),
defer.reject.bind(defer)
);
} catch (e) {
defer.reject(e);
}
});
} else {
setTimeout(function () {
defer.reject.apply(defer, that._answers);
});
}
break;
case UNRESOLVED:
if (typeof onSuccess === 'function') {
this._onResolve.push(function () {
try {
thenItem(
onSuccess.apply(that, arguments),
defer.resolve.bind(defer),
defer.reject.bind(defer),
defer.notify.bind(defer)
);
} catch (e) {
defer.reject(e);
}
});
} else {
this._onResolve.push(function () {
defer.resolve.apply(defer, arguments);
});
}
if (typeof onError === 'function') {
this._onReject.push(function () {
try {
thenItem(
onError.apply(that, that._answers),
defer.reject.bind(defer),
defer.reject.bind(defer)
);
} catch (e) {
defer.reject(e);
}
});
} else {
this._onReject.push(function () {
defer.reject.apply(defer, that._answers);
});
}
if (typeof onProgress === 'function') {
this._onProgress.push(function () {
var result;
try {
result = onProgress.apply(that, arguments);
if (result === undefined) {
defer.notify.apply(defer, arguments);
} else {
defer.notify(result);
}
} catch (e) {
defer.notify.apply(defer, arguments);
}
});
} else {
this._onProgress.push(function () {
defer.notify.apply(defer, arguments);
});
}
break;
default:
break;
}
return next;
};
// p.resolve() ?
// p.reject() ?
// p.notify() ?
/**
* p.cancel(): p
*
* Cancels the operation by calling promise._cancel().
*
* @method cancel
* @return {Promise} this
*/
Promise.prototype.cancel = function () {
if (this._state === UNRESOLVED) {
this._state = CANCELLED;
if (typeof this._cancel === 'function') {
this._cancel();
}
this._onResolve = undefined;
this._onReject = undefined;
this._onProgress = undefined;
}
return this;
};
/**
* p.timeout(delay): p
*
* Reject the promise with an Error("Timeout") and cancel the operation.
*
* @method timeout
* @param {Number} delay The delay before rejection
* @return {Promise} this
*/
Promise.prototype.timeout = function (delay) {
return exports.choose(this, exports.delay(delay).then(function () {
throw new Error("Timeout (" + delay + ")");
}));
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/A
// get(propertyName)
/**
* get(property): Promise
*
* Get the property of the promise response as first parameter of the new
* Promise.
*
* Deferred.when({'a': 'b'}).get('a').then(console.log); // shows 'b'
*
* @method get
* @param {String} property The object property name
* @return {Promise} The promise
*/
Promise.prototype.get = function (property) {
return this.then(function (dict) {
return dict[property];
});
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/A
// call(functionName, arg1, arg2, ...)
/**
* call(function_name, *args): Promise
*
* Deferred.when({'a': console.log}).call('a', 'b'); // shows 'b'
*
* @method call
* @param {String} function_name The function to call
* @param {Any} *[args] The function arguments
* @return {Promise} A new promise
*/
Promise.prototype.call = function (function_name) {
var args = Array.prototype.slice.call(arguments, 1);
return this.then(function (dict) {
return dict[function_name].apply(dict, args);
});
};
/**
* put(property, value): Promise
*
* Put a property value from a promise response and return the registered
* value as first parameter of the new Promise.
*
* Deferred.when({'a': 'b'}).put('a', 'c').then(console.log); // shows 'c'
*
* @method put
* @param {String} property The object property name
* @param {Any} value The value to put
* @return {Promise} A new promise
*/
Promise.prototype.put = function (property, value) {
return this.then(function (dict) {
dict[property] = value;
return dict[property];
});
};
/**
* del(property): Promise
*
* Delete a property value from a promise response and return the property
* value as first parameter of the new Promise.
*
* Deferred.when({'a': 'b'}).del('a').then(console.log);
* // shows undefined
*
* @method del
* @param {String} property The object property name
* @return {Promise} A new promise
*/
Promise.prototype.del = function (property) {
return this.then(function (dict) {
delete dict[property];
return dict[property];
});
};
/**
* p.done(callback): p
*
* Call the callback on resolve.
*
* Deferred.when(1).
* done(function (one) { return one + 1; }).
* done(console.log); // shows 1
*
* @method done
* @param {Function} callback The callback to call on resolve
* @return {Promise} This promise
*/
Promise.prototype.done = function (callback) {
var that = this;
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case RESOLVED:
setTimeout(function () {
try {
callback.apply(that, that._answers);
} catch (ignore) {} // errors will never be retrieved by global
});
break;
case UNRESOLVED:
this._onResolve.push(callback);
break;
default:
break;
}
return this;
};
/**
* p.fail(callback): p
*
* Call the callback on reject.
*
* promisedTypeError().
* fail(function (e) { name_error(); }).
* fail(console.log); // shows TypeError
*
* @method fail
* @param {Function} callback The callback to call on reject
* @return {Promise} This promise
*/
Promise.prototype.fail = function (callback) {
var that = this;
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case REJECTED:
setTimeout(function () {
try {
callback.apply(that, that._answers);
} catch (ignore) {} // errors will never be retrieved by global
});
break;
case UNRESOLVED:
this._onReject.push(callback);
break;
default:
break;
}
return this;
};
/**
* p.progress(callback): p
*
* Call the callback on notify.
*
* Promise.delay(100, 10).
* progress(function () { return null; }).
* progress(console.log); // does not show null
*
* @method progress
* @param {Function} callback The callback to call on notify
* @return {Promise} This promise
*/
Promise.prototype.progress = function (callback) {
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case UNRESOLVED:
this._onProgress.push(callback);
break;
default:
break;
}
return this;
};
/**
* p.always(callback): p
*
* Call the callback on resolve or on reject.
*
* sayHello().
* done(iAnswer).
* fail(iHeardNothing).
* always(iKeepWalkingAnyway);
*
* @method always
* @param {Function} callback The callback to call on resolve or on reject
* @return {Promise} This promise
*/
Promise.prototype.always = function (callback) {
var that = this;
if (typeof callback !== 'function') {
return this;
}
switch (this._state) {
case RESOLVED:
case REJECTED:
setTimeout(function () {
try {
callback.apply(that, that._answers);
} catch (ignore) {} // errors will never be retrieved by global
});
break;
case UNRESOLVED:
that._onReject.push(callback);
that._onResolve.push(callback);
break;
default:
break;
}
return this;
};
exports.Promise = Promise;
/**
* Deferred(cancel)
*
* @class Deferred
* @constructor
*/
function Deferred(cancel) {
this.promise = new Promise(cancel);
}
/**
* resolve(*args): any
*
* Resolves the promise with the given arguments.
*
* @method resolve
* @param {Any} *[args] The arguments to give
*/
Deferred.prototype.resolve = function () {
return promiseResolve(this.promise, arguments);
};
/**
* reject(*args): any
*
* Rejects the promise with the given arguments.
*
* @method reject
* @param {Any} *[args] The arguments to give
*/
Deferred.prototype.reject = function () {
return promiseReject(this.promise, arguments);
};
/**
* notify(*args): any
*
* Notifies the promise with the given arguments.
*
* @method notify
* @param {Any} *[args] The arguments to give
*/
Deferred.prototype.notify = function () {
return promiseNotify(this.promise, arguments);
};
exports.Deferred = Deferred;
//////////////////////////////////////////////////////////////////////
// Inspired by Task.js
/**
* now(value): Promise
*
* Converts an ordinary value into a fulfilled promise.
*
* @param {Any} value The value to use
* @return {Promise} The resolved promise
*/
exports.now = function (value) {
var deferred = new Deferred();
deferred.resolve(value);
return deferred.promise;
};
/**
* join(*promises): Promise
*
* Produces a promise that is resolved when all the given promises are
* resolved. The resolved value is an array of each of the resolved values of
* the given promises.
*
* If any of the promises is rejected, the joined promise is rejected with the
* same error, and any remaining unfulfilled promises are cancelled.
*
* @param {Promise} *[promises] The promises to join
* @return {Promise} A new promise
*/
exports.join = function () {
var promises, results = [], i, count = 0, deferred;
promises = Array.prototype.slice.call(arguments);
function cancel() {
var j;
for (j = 0; j < promises.length; j += 1) {
if (typeof promises[j].cancel === 'function') {
promises[j].cancel();
}
}
}
deferred = new Deferred(cancel);
function succeed(j) {
return function (answer) {
results[j] = answer;
count += 1;
if (count !== promises.length) {
return;
}
deferred.resolve(results);
};
}
function failed(answer) {
cancel();
deferred.reject(answer);
}
function notify(j) {
return function (answer) {
deferred.notify({
"promise": this,
"index": j,
"answer": answer
});
};
}
for (i = 0; i < promises.length; i += 1) {
promises[i].then(succeed(i), failed, notify(i));
}
return deferred.promise;
};
/**
* choose(*promises): Promise
*
* Produces a promise that is fulfilled when any one of the given promises is
* fulfilled. As soon as one of the promises is fulfilled, whether by being
* resolved or rejected, all the other promises are cancelled.
*
* @param {Promise} *[promises] The promises to use
* @return {Promise} A new promise
*/
exports.choose = function () {
var promises, i, deferred;
promises = Array.prototype.slice.call(arguments);
function cancel() {
var j;
for (j = 0; j < promises.length; j += 1) {
if (typeof promises[j].cancel === 'function') {
promises[j].cancel();
}
}
}
deferred = new Deferred(cancel);
function succeed(answer) {
cancel();
deferred.resolve(answer);
}
function failed(answer) {
cancel();
deferred.reject(answer);
}
function notify(j) {
return function (answer) {
deferred.notify({
"promise": this,
"index": j,
"answer": answer
});
};
}
for (i = 0; i < promises.length; i += 1) {
promises[i].then(succeed, failed, notify(i));
}
return deferred.promise;
};
/**
* never(): Promise
*
* Produces a promise that is never fulfilled.
*
* @return {Promise} A promise
*/
exports.never = function () {
return new Promise();
};
/**
* sleep(delay[, every]): Promise
*
* Resolve the promise after `timeout` milliseconds and notfies us every
* `every` milliseconds.
*
* Deferred.delay(50, 10).then(console.log, console.error, console.log);
* // // shows
* // 10 // from progress
* // 20 // from progress
* // 30 // from progress
* // 40 // from progress
* // 50 // from progress
* // 50 // from success
*
* @param {Number} delay In milliseconds
* @param {Number} [every] In milliseconds
* @return {Promise} A new promise
*/
exports.sleep = function (delay, every) {
var deferred, timeout, interval, now = 0;
function cancel() {
clearTimeout(timeout);
clearInterval(interval);
}
deferred = new Deferred(cancel);
if (typeof every === 'number' && isFinite(every)) {
interval = setInterval(function () {
now += every;
deferred.notify(now);
}, every);
}
timeout = setTimeout(function () {
clearInterval(interval);
deferred.notify(delay);
deferred.resolve(delay);
}, delay);
return deferred.promise;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// when(value, callback, errback_opt)
/**
* when(item, [onSuccess], [onError], [onProgress]): Promise
*
* Return an item as first parameter of the promise answer. If item is of
* type Promise, the method will just return the promise. If item is of type
* Deferred, the method will return the deferred promise.
*
* Deferred.when('a').then(console.log); // shows 'a'
*
* @param {Any} item The item to use
* @param {Function} [onSuccess] The callback called on success
* @param {Function} [onError] the callback called on error
* @param {Function} [onProgress] the callback called on progress
* @return {Promise} The promise
*/
exports.when = function (item, onSuccess, onError, onProgress) {
if (typeof item === 'object' && item !== null) {
if (typeof item.promise === 'object' && item.promise !== null &&
typeof item.promise.then === 'function') {
// item seams to be a Deferred
return item.promise.then(onSuccess, onError, onProgress);
}
if (typeof item.then === 'function') {
// item seams to be a Promise
return item.then(onSuccess, onError, onProgress);
}
}
// item is just a value, convert into fulfilled promise
var deferred = new Deferred(), promise;
if (typeof onSuccess === 'function') {
promise = deferred.promise.then(onSuccess);
} else {
promise = deferred.promise;
}
deferred.resolve(item);
return promise;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// get(object, name)
/**
* get(dict, property): Promise
*
* Return the dict property as first parameter of the promise answer.
*
* Deferred.get({'a': 'b'}, 'a').then(console.log); // shows 'b'
*
* @param {Object} dict The object to use
* @param {String} property The object property name
* @return {Promise} The promise
*/
exports.get = function (dict, property) {
var p = new Deferred();
try {
p.resolve(dict[property]);
} catch (e) {
p.reject(e);
}
return p;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// put(object, name, value)
/**
* put(dict, property, value): Promise
*
* Set and return the dict property as first parameter of the promise answer.
*
* Deferred.put({'a': 'b'}, 'a', 'c').then(console.log); // shows 'c'
*
* @param {Object} dict The object to use
* @param {String} property The object property name
* @param {Any} value The value
* @return {Promise} The promise
*/
exports.put = function (dict, property, value) {
var p = new Deferred();
try {
dict[property] = value;
p.resolve(dict[property]);
} catch (e) {
p.reject(e);
}
return p;
};
////////////////////////////////////////////////////////////
// http://wiki.commonjs.org/wiki/Promises/B
// del(object, name)
/**
* del(dict, property): Promise
*
* Delete and return the dict property as first parameter of the promise
* answer.
*
* Deferred.del({'a': 'b'}, 'a').then(console.log); // shows undefined
*
* @param {Object} dict The object to use
* @param {String} property The object property name
* @return {Promise} The promise
*/
exports.del = function (dict, property) {
var p = new Deferred();
try {
delete dict[property];
p.resolve(dict[property]);
} catch (e) {
p.reject(e);
}
return p;
};
}));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment