Files
private-cloud/console/web/public_js/chinese/await.js
2020-01-12 19:15:08 +08:00

529 lines
15 KiB
JavaScript

// ########################################################################
// PROLOGUE - LICENSE
/*
Copyright (c) 2013 by Greg Reimer
https://github.com/greim
http://obadger.com/
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function(){
// ------------------------------------------------------------------------
// CLOSURE SCOPE VARS
var slice = Array.prototype.slice;
// ------------------------------------------------------------------------
// PROGRESS CLASS
function Progress(){}
Progress.prototype = {
getAverage: function(){
var names = Object.keys(this);
var total = 0;
for (var i=0; i<names.length; i++) {
total += this[names[i]];
}
var average = total / names.length;
return average;
}
};
// ------------------------------------------------------------------------
// GOTTEN CLASS
function Gotten(){}
Gotten.prototype = {
forEach: Array.prototype.forEach,
map: Array.prototype.map,
some: Array.prototype.some,
every: Array.prototype.every,
reduce: Array.prototype.reduce,
slice: Array.prototype.slice,
join: Array.prototype.join,
keys: function(){
return Object.keys(this);
},
values: function(){
return Object.keys(this).map(function(name){
return this[name];
}, this)
}
};
// ------------------------------------------------------------------------
// PROMISE CLASS
function Promise() {
this._progress = new Progress();
this._built = false;
this._slots = {};
this._got = new Gotten();
this._allbacks = [];
this._failure = false;
this._success = false;
}
Promise.prototype = {
// ------------------------------------------------------------------------
/**
Runs the given callback, passing it an instance of the promise,
and then returning that same instance.
*/
run: function(cb, ctx){
cb.call(ctx, this);
return this;
},
// ------------------------------------------------------------------------
_on: function(event, cb, ctx){
if (typeof cb !== 'function') {
throw new Error('No callback provided.');
}
// NOTE: It's possible for both this._success and this._failure
// to be true at the same time. In such cases this._success
// pre-empts this._failure.
if (this._success || this._failure) {
// timeout guarantees cb gets
// executed after return
var thisProm = this;
setTimeout(function(){
if (event === 'resolve') {
cb.call(ctx);
}
if (thisProm._success) {
if (event === 'keep') {
cb.call(ctx, thisProm._got);
}
} else {
if (event === 'fail') {
cb.apply(ctx, thisProm._failure);
}
}
},0);
} else {
this._allbacks.push({
callback:cb,
context:ctx,
type:event
});
}
return this;
},
// ------------------------------------------------------------------------
onfail: function(cb, ctx){
return this._on('fail', cb, ctx);
},
// ------------------------------------------------------------------------
onkeep: function(cb, ctx){
return this._on('keep', cb, ctx);
},
// ------------------------------------------------------------------------
onresolve: function(cb, ctx){
return this._on('resolve', cb, ctx);
},
// ------------------------------------------------------------------------
onprogress: function(cb, ctx){
return this._on('progress', cb, ctx);
},
// ------------------------------------------------------------------------
failer: function(){
var thisProm = this;
return function(){
return Promise.prototype.fail.apply(thisProm, arguments);
};
},
// ------------------------------------------------------------------------
progress: function(){
if (!this._allbacks) {
return this;
}
var amounts = arguments[0];
if (typeof arguments[0] === 'string' && typeof arguments[1] === 'number') {
amounts = {};
amounts[arguments[0]] = arguments[1];
}
Object.keys(amounts).forEach(function(name){
var amount = amounts[name];
if (this._slots[name] === undefined) {
return;
}
amount = parseFloat(amount) || 0;
amount = Math.max(Math.min(amount, 1), 0);
this._progress[name] = amount;
}, this);
this._allbacks.forEach(function(allback){
if (allback.type === 'progress') {
allback.callback.call(allback.context, this._progress);
}
}, this);
return this;
},
// ------------------------------------------------------------------------
/*
Keep part of the promise.
*/
keep: function(name, data){
if (data === undefined) {
data = null;
}
if (this._got[name] !== undefined){
return this;
}
if (this._slots[name] === undefined) {
this._got[name] = data;
return this;
}
if (!this._failure && !this._success){
this._progress[name] = 1;
this._slots[name] = true;
this._got[name] = data;
var kept = Object.keys(this._slots).every(function(item){
return this._slots[item];
}, this);
if (kept) {
this._success = true;
this._allbacks.filter(function(obj){
return obj.type === 'keep' || obj.type === 'resolve';
}).forEach(function(obj){
if (obj.type === 'keep') {
obj.callback.call(obj.context, this._got);
} else { // it's resolve
obj.callback.call(obj.context);
}
}, this);
// these are no longer needed, allow GC
this._allbacks = undefined;
}
}
return this;
},
// ------------------------------------------------------------------------
/*
Fail the promise for some reason.
*/
fail: function(){
if (!this._failure && !this._success){
this._failure = slice.call(arguments);
this._allbacks.filter(function(obj){
return obj.type === 'fail' || obj.type === 'resolve';
}).forEach(function(obj){
if (obj.type === 'fail') {
obj.callback.apply(obj.context, this._failure);
} else { // resolve
obj.callback.call(obj.context);
}
}, this);
// these are no longer needed, allow GC
this._allbacks = undefined;
}
return this;
},
// ------------------------------------------------------------------------
/**
Convenience function for error-first Node JS callback convention.
*/
nodify: function(){
var items = slice.call(arguments);
var thisProm = this;
return function(err){
if (err) {
thisProm.fail(err);
} else {
var args = slice.call(arguments);
if (typeof items[0] === 'function') {
var cb = items[0];
var ctx = items[1];
cb.apply(ctx, args);
} else {
args.shift(); // lose the error
items.forEach(function(thing, idx){
if (thing !== null && thing !== undefined) {
thisProm.keep(thing, args[idx]);
}
});
}
}
};
},
// ------------------------------------------------------------------------
take: function(p2, map){
if (this._success || this._failure) {
// do nothing
} else if (p2 instanceof Promise) {
p2.onfail(this.failer());
p2.onkeep(function(got){
var taken = {}, gotItems = Object.keys(got);
// take any direct matches first
gotItems.forEach(function(item){
if (this._slots.hasOwnProperty(item)) {
taken[item] = got[item];
}
}, this);
// take matches via mapping, overwrites any direct matches
if (map) {
gotItems.forEach(function(item){
if (map.hasOwnProperty(item) && this._slots.hasOwnProperty(map[item])){
taken[map[item]] = got[item];
}
}, this);
}
Object.keys(taken).forEach(function(item){
this.keep(item, taken[item]);
}, this);
}, this);
} else if (p2 && typeof p2.then === 'function' && typeof map === 'string') {
var name = map;
var thisProm = this;
p2.then(function(val){
thisProm.keep(name, val);
},function(err){
thisProm.fail(err);
},function(amount){
thisProm.progress(name, amount);
});
}
return this;
},
// ------------------------------------------------------------------------
map: function(map){
map || (map = {});
var items = [];
Object.keys(this._slots).forEach(function(item){
if (map.hasOwnProperty(item)) {
items.push(map[item]);
} else {
items.push(item);
}
});
return await.apply(this, items).take(this, map);
},
// ------------------------------------------------------------------------
then: (function(){
// private helper
function defaultFulfilled(){ return this; }
function defaultRejected(err){ throw err; }
function fulfillWithResult(thenProm, returned, got) {
if (returned instanceof await) {
thenProm._buildState(returned);
} else {
var valueProm = await('value').run(function(prom){
if (returned && typeof returned.then === 'function') {
returned.then(function(val) {
prom.keep('value', val);
},function(reason) {
prom.fail(reason);
});
} else {
// 'returned' is some value other than a promise
prom.keep('value', returned);
}
});
thenProm._buildState(valueProm);
}
// accumulate values, as long as the old
// values don't clobber the new ones
if (got) {
got.keys().forEach(function(name){
if (!thenProm._slots.hasOwnProperty(name)) {
thenProm.keep(name, got[name]);
}
});
}
}
return function(onFulfilled, onRejected, onProgress) {
if (typeof onFulfilled !== 'function') {
onFulfilled = defaultFulfilled;
}
if (typeof onRejected !== 'function') {
onRejected = defaultRejected;
}
var thisProm = this;
// empty promise so it can build state from a future promise
return await().run(function(thenProm) {
thisProm
.onkeep(function(got) {
try {
var returnedValue = onFulfilled.call(thisProm, got);
fulfillWithResult(thenProm, returnedValue, got);
} catch(ex) {
thenProm.fail(ex);
}
})
.onfail(function(reason) {
try {
var returnedValue = onRejected.call(thisProm, reason);
fulfillWithResult(thenProm, returnedValue);
} catch(ex) {
thenProm.fail(ex);
}
});
if (typeof onProgress === 'function') {
thisProm.onprogress(function(progress) {
try {
// make sure to call the prototype getAverage() in case there's a slot named "getAverage"
onProgress.call(thisProm, Progress.prototype.getAverage.call(thisProm._progress));
} catch(ex) {}
});
}
});
};
})(),
// ------------------------------------------------------------------------
catch: function(onRejected){
return this.then(null, onRejected);
},
// ------------------------------------------------------------------------
_buildState: function() {
var items = slice.call(arguments);
// Check if already built.
if (this._built) {
throw new Error('cannot build state twice');
} else {
this._built = items.length > 0;
}
// Populate slots.
items.forEach(function(item) {
if (item instanceof Promise) {
Object.keys(item._slots).forEach(function(item) {
this._slots[item] = false;
}, this);
} else {
this._slots[item] = false;
}
}, this);
// Having populated slots, take promises.
items.forEach(function(item) {
if (item instanceof Promise) {
this.take(item);
}
}, this);
Object.keys(this._slots).forEach(function(slot){
this._progress[slot] = 0;
}, this);
return this;
}
};
// ------------------------------------------------------------------------
// FACTORY FUNCTION
// this is the function exported
var await = function(){
var prom = new Promise();
prom._buildState.apply(prom, arguments);
return prom;
};
// ------------------------------------------------------------------------
// AWAITING LISTS
await.all = function(list) {
if (!list || list.length === 0) {
return await('length').keep('length',0);
}
var keys = list.map(function(prom, idx){
return idx;
});
keys.push('length');
return await.apply(this, keys).run(function(allProm){
allProm.keep('length', list.length);
list.forEach(function(prom, idx){
prom.onfail(allProm.failer());
prom.onkeep(function(got){
allProm.keep(idx, got);
});
});
});
};
// ------------------------------------------------------------------------
// INSTANCEOF SUPPORT
// so that "foo instanceof await" works
await.prototype = Promise.prototype;
// ------------------------------------------------------------------------
// EXPORT
// for browsers
try {
if (typeof define === 'function' && define.amd) {
define('await', [], function(){ return await; });
} else {
window.await = await;
}
} catch(err) {}
// for node
try {
module.exports = await;
// back compat, for people calling this lib
// like var await = require('await').await
module.exports.await = await;
} catch(err) {}
})();