UNPKG

promise-thunk

Version:
737 lines (628 loc) 23.3 kB
// PromiseThunk void function (global, PromiseOrg) { 'use strict'; var slice = [].slice; var hasConsole = typeof console === 'object' && console !== null; var hasConsoleWarn = hasConsole && typeof console.warn === 'function'; var hasConsoleError = hasConsole && typeof console.error === 'function'; var COLOR_ERROR = typeof window !== 'undefined' ? '' : '\x1b[35m'; var COLOR_NORMAL = typeof window !== 'undefined' ? '' : '\x1b[m'; // Object.keys for ie8 if (!Object.keys) Object.keys = function keys(obj) { var props = []; for (var prop in obj) if (obj.hasOwnProperty(prop)) props.push(prop); return props; }, hasConsoleWarn && console.warn('Undefined: Object.keys'); // Object.getOwnPropertyNames for ie8 if (!Object.getOwnPropertyNames) Object.getOwnPropertyNames = Object.keys, hasConsoleWarn && console.warn('Undefined: Object.getOwnPropertyNames'); // Array.prototype.reduce for ie8 if (!Array.prototype.reduce) Array.prototype.reduce = function reduce(fn, val) { var i = 0; if (arguments.length <= 1) val = this[i++]; for (var n = this.length; i < n; ++i) val = fn(val, this[i], i, this); return val; }, hasConsoleWarn && console.warn('Undefined: Array.prototype.reduce'); var COLORS = {red: '31', green: '32', purple: '35', cyan: '36', yellow: '33'}; var colors = Object.keys(COLORS).reduce(function (obj, k) { obj[k] = typeof window === 'object' ? function (x) { return x; } : function (x) { return '\x1b[' + COLORS[k] + 'm' + x + '\x1b[m'; }; return obj; }, {}); function errmsg(err) { return err.stack || err; } // defProp(obj, prop, propDesc) var defProp = function (obj) { if (!Object.defineProperty) return null; try { Object.defineProperty(obj, 'prop', {value: 'str'}); return obj.prop === 'str' ? Object.defineProperty : null; } catch (err) { return null; } } ({}); // setConst(obj, prop, val) var setConst = defProp ? function setConst(obj, prop, val) { defProp(obj, prop, {value: val}); } : function setConst(obj, prop, val) { obj[prop] = val; }; // setValue(obj, prop, val) var setValue = defProp ? function setValue(obj, prop, val) { defProp(obj, prop, {value: val, writable: true, configurable: true}); } : function setValue(obj, prop, val) { obj[prop] = val; }; // getProto(obj) var getProto = Object.getPrototypeOf || function getProto(obj) { return obj.__proto__; }; // setProto(obj, proto) var setProto = Object.setPrototypeOf || function (obj, proto) { obj.__proto__ = proto; }; // BaseClass.extend(proto, statics) function extend(proto, statics) { var ctor = proto.constructor; function super_() { setValue(this, 'constructor', ctor); } if (typeof this === 'function') super_.prototype = this.prototype, ctor.prototype = new super_(); for (var p in proto) if (proto.hasOwnProperty(p) && !ctor.prototype.hasOwnProperty(p)) setValue(ctor.prototype, p, proto[p]); for (var p in statics) if (statics.hasOwnProperty(p)) setValue(ctor, p, statics[p]); return ctor; } // nextTickDo(fn) var nextTickDo = typeof process === 'object' && process && typeof process.nextTick === 'function' ? process.nextTick : typeof setImmediate === 'function' ? setImmediate : function nextTickDo(fn) { setTimeout(fn, 0); }; // nextExec(ctx, fn) var nextExec = function () { // tasks {head, tail} var tasks = {head:undefined, tail:undefined}; var progress = false; // nextExec(ctx, fn) function nextExec(ctx, fn) { var task = {ctx:ctx, fn:fn, chain:undefined}; tasks.tail = tasks.tail ? (tasks.tail.chain = task) : (tasks.head = task); if (progress) return; progress = true; nextTickDo(nextTickExec); } function nextTickExec() { var task; while (task = tasks.head) { tasks.head = task.chain; task.chain = undefined; if (!tasks.head) tasks.tail = undefined; var fn = task.fn; fn(task.ctx); } progress = false; } return nextExec; }(); // nextExec var PROMISE_FLAG_RESOLVED = 1; var PROMISE_FLAG_REJECTED = 2; var PROMISE_FLAG_SOLVED = PROMISE_FLAG_RESOLVED | PROMISE_FLAG_REJECTED; var PROMISE_FLAG_HANDLED = 4; var PROMISE_FLAG_UNHANDLED_REJECTION = 8; var PROMISE_FLAG_UNHANDLED = PROMISE_FLAG_HANDLED | PROMISE_FLAG_UNHANDLED_REJECTION; // new Promise(function setup(resolve, reject) {}) var Promise = extend({ constructor: function Promise(setup) { if (!(this instanceof Promise)) throw new TypeError('new Promise!!!'); thunk.constructor = Promise; thunk.then = then; thunk['catch'] = caught; thunk.toString = toString; thunk.toJSON = toJSON; thunk.flag = 0; thunk.result = undefined; thunk.tail = thunk.head = undefined; try { setup(resolve, reject); } catch (err) { reject(err); } return thunk; function thunk(cb) { return $$thunk(thunk, cb); } function resolve(val) { return $$resolve(thunk, val); } function reject(err) { return $$reject(thunk, err); } }, // Promise then: then, 'catch': caught, toString: toString, toJSON: toJSON }, { // statics all: all, race: race, defer: defer, resolve: resolve, reject: reject, accept: resolve, convert: resolve, wrap: promisify, promisify: promisify, thunkify: thunkify, promisifyAll: promisifyAll, thunkifyAll: thunkifyAll, isIterable: isIterable, isIterator: isIterator, isPromise: isPromise, makeArrayFromIterator: makeArrayFromIterator }); // Promise // new PromiseResolved(val) function PromiseResolved(val) { thunk.constructor = Promise; thunk.then = then; thunk['catch'] = caught; thunk.toString = toString; thunk.toJSON = toJSON; thunk.flag = PROMISE_FLAG_RESOLVED; thunk.result = val; thunk.tail = thunk.head = undefined; return thunk; function thunk(cb) { return $$thunk(thunk, cb); } } // PromiseResolved PromiseResolved.prototype = Promise.prototype; // new PromiseRejected(err) function PromiseRejected(err) { thunk.constructor = Promise; thunk.then = then; thunk['catch'] = caught; thunk.toString = toString; thunk.toJSON = toJSON; thunk.flag = PROMISE_FLAG_REJECTED; thunk.result = err; thunk.tail = thunk.head = undefined; nextExec(thunk, $$fire); return thunk; function thunk(cb) { return $$thunk(thunk, cb); } } // PromiseRejected PromiseRejected.prototype = Promise.prototype; // new PromiseNext(parent, reject, resolve) function PromiseNext(parent, reject, resolve, cb) { thunk.constructor = Promise; thunk.then = then; thunk['catch'] = caught; thunk.toString = toString; thunk.toJSON = toJSON; thunk.flag = 0; thunk.result = undefined; thunk.tail = thunk.head = undefined; var bomb = {rej:reject, res:resolve, cb:cb, thunk:thunk, chain:undefined}; parent.tail = parent.tail ? (parent.tail.chain = bomb) : (parent.head = bomb); if (parent.flag & PROMISE_FLAG_SOLVED) nextExec(parent, $$fire); return thunk; function thunk(cb) { return $$thunk(thunk, cb); } } // PromiseNext PromiseNext.prototype = Promise.prototype; // new PromiseDefer() function PromiseDefer() { thunk.constructor = Promise; thunk.then = then; thunk['catch'] = caught; thunk.toString = toString; thunk.toJSON = toJSON; thunk.flag = 0; thunk.result = undefined; thunk.tail = thunk.head = undefined; return {promise:thunk, resolve:resolve, reject:reject}; function thunk(cb) { return $$thunk(thunk, cb); } function resolve(val) { return $$resolve(thunk, val); } function reject(err) { return $$reject(thunk, err); } } // PromiseDefer PromiseDefer.prototype = Promise.prototype; // new PromiseConvert(thenable) function PromiseConvert(thenable) { thunk.constructor = Promise; thunk.then = then; thunk['catch'] = caught; thunk.toString = toString; thunk.toJSON = toJSON; thunk.flag = 0; thunk.result = undefined; thunk.tail = thunk.head = undefined; thenable.then(resolve, reject); return thunk; function thunk(cb) { return $$thunk(thunk, cb); } function resolve(val) { return $$resolve(thunk, val); } function reject(err) { return $$reject(thunk, err); } } // PromiseConvert PromiseConvert.prototype = Promise.prototype; // Promise.resolve(val) function resolve(val) { if (isPromise(val)) return new PromiseConvert(val); return new PromiseResolved(val); } // Promise.reject(err) function reject(err) { return new PromiseRejected(err); } // Promise.defer() function defer() { return new PromiseDefer(); } // Promise#toString() function toString() { return colors.cyan('PromiseThunk { ') + ( this.flag & PROMISE_FLAG_RESOLVED ? colors.green(this.result) : this.flag & PROMISE_FLAG_REJECTED ? colors.red('<rejected> [' + this.result + ']') : colors.yellow('<pending>')) + colors.cyan(' }'); } // Promise#toJSON() function toJSON() { var obj = {'class': 'PromiseThunk'}; obj.state = ['pending', 'resolved', 'rejected'][this.flag & PROMISE_FLAG_SOLVED]; if (this.flag & PROMISE_FLAG_RESOLVED) obj.value = this.result; if (this.flag & PROMISE_FLAG_REJECTED) obj.error = this.result; return obj; } // Promise#then(resolve, reject) function then(resolve, reject) { return new PromiseNext(this, reject, resolve, undefined); } // Promise#catch(reject) function caught(reject) { return new PromiseNext(this, reject, undefined, undefined); } // $$thunk(thunk, cb) function $$thunk(thunk, cb) { return new PromiseNext(thunk, undefined, undefined, cb); } // $$resolve(thunk, val function $$resolve(thunk, val) { if (thunk.flag & PROMISE_FLAG_SOLVED) return; if (isPromise(val)) return val.then( function (v) { return $$resolve(thunk, v); }, function (e) { return $$reject(thunk, e); }); thunk.result = val; thunk.flag = PROMISE_FLAG_RESOLVED; if (thunk.head) nextExec(thunk, $$fire); } // $$resolve // $$reject(thunk, err) function $$reject(thunk, err) { if (thunk.flag & PROMISE_FLAG_RESOLVED) return hasConsoleError && console.error(colors.yellow('* Resolved promise rejected: ') + thunk + '\n' + colors.purple('* ' + errmsg(err))); if (thunk.flag & PROMISE_FLAG_REJECTED) return hasConsoleError && console.error(colors.yellow('* Rejected promise rejected: ') + thunk + '\n' + colors.purple('* ' + errmsg(err))); thunk.result = (typeof err === 'object' && err instanceof Error) ? err : Error(err); thunk.flag = PROMISE_FLAG_REJECTED; nextExec(thunk, $$fire); } // $$reject // $$callback(thunk, err, val) function $$callback(thunk, err, val) { return err ? $$reject(thunk, err) : $$resolve(thunk, val); } // thunk.$$callback2(err, val, ...) function $$callback2(err, val) { switch (arguments.length) { case 2: return err instanceof Error ? $$reject(this, err) : $$resolve(this, val); case 1: return err instanceof Error ? $$reject(this, err) : $$resolve(this, err); case 0: return $$resolve(this); default: return err instanceof Error ? $$reject(this, err) : $$resolve(this, slice.call(arguments, 1)); } } // $$fire(thunk) function $$fire(thunk) { if (!(thunk.flag & PROMISE_FLAG_SOLVED)) return; if (thunk.flag & PROMISE_FLAG_REJECTED) var err = thunk.result; else var val = thunk.result, err = null; var bomb; while (bomb = thunk.head) { thunk.head = bomb.chain; bomb.chain = undefined; if (!thunk.head) thunk.tail = undefined; fire(bomb.thunk, err, val, bomb.rej, bomb.res, bomb.cb); if ((thunk.flag & PROMISE_FLAG_UNHANDLED) === PROMISE_FLAG_UNHANDLED_REJECTION) $$rejectionHandled(thunk); thunk.flag |= PROMISE_FLAG_HANDLED; } if (thunk.flag === PROMISE_FLAG_REJECTED) nextExec(thunk, $$checkUnhandledRejection); } // $$fire function fire(thunk, err, val, rej, res, cb) { try { var r = cb ? ( cb.length == 2 ? cb(err, val) : cb.length == 1 ? err ? cb(err) : cb(val) : cb.length == 0 ? cb(err, val) : cb.apply(null, [err].concat(val)) ) : err ? (rej ? rej(err) : err) : res ? res(val) : val; firebytype[typeof r](thunk, r); } catch (e) { $$reject(thunk, e); } } // fire var firebytype = { number: $$resolve, string: $$resolve, boolean: $$resolve, undefined: $$resolve, object: function (thunk, r) { if (r === null) $$resolve(thunk, r); else if (typeof r.then === 'function') r.then( function (v) { return $$resolve(thunk, v); }, function (e) { return $$reject(thunk, e); }); else if (r instanceof Error) $$reject(thunk, r); else $$resolve(thunk, r); }, 'function': function (thunk, r) { if (typeof r.then === 'function') r.then( function (v) { return $$resolve(thunk, v); }, function (e) { return $$reject(thunk, e); }); else r(function () { return $$callback2.apply(thunk, arguments); }); } }; // $$checkUnhandledRejection(thunk) function $$checkUnhandledRejection(thunk) { if (!(thunk.flag & PROMISE_FLAG_UNHANDLED)) $$unhandledRejection(thunk); } // $$unhandledRejection(thunk) function $$unhandledRejection(thunk) { thunk.flag |= PROMISE_FLAG_UNHANDLED_REJECTION; if (typeof process === 'object' && process && typeof process.on === 'function') process.emit('unhandledRejection', thunk.result, thunk); hasConsoleError && console.error(colors.yellow('* UnhandledRejection: ') + thunk + colors.purple('\n* ' + errmsg(thunk.result))); } // $$rejectionHandled(thunk) function $$rejectionHandled(thunk) { if (typeof process === 'object' && process && typeof process.on === 'function') process.emit('rejectionHandled', thunk); hasConsoleError && console.error(colors.green('* RejectionHandled: ') + thunk); } // Promise.all([p, ...]) function all(promises) { if (isIterator(promises)) promises = makeArrayFromIterator(promises); if (!(promises instanceof Array)) throw new TypeError('promises must be an array'); return new Promise( function promiseAll(resolve, reject) { var n = promises.length; if (n === 0) return resolve([]); var res = Array(n); promises.forEach(function (p, i) { function complete(val) { res[i] = val; if (--n === 0) resolve(res); } if (isPromise(p)) return p.then(complete, reject); complete(p); }); // promises.forEach } ); // return new Promise } // all // Promise.race([p, ...]) function race(promises) { if (isIterator(promises)) promises = makeArrayFromIterator(promises); if (!(promises instanceof Array)) throw new TypeError('promises must be an array'); return new Promise( function promiseRace(resolve, reject) { promises.forEach(function (p) { if (isPromise(p)) return p.then(resolve, reject); resolve(p); }); // promises.forEach } ); // return new Promise } // race // isPromise(p) function isPromise(p) { return (typeof p === 'object' && !!p || typeof p === 'function') && typeof p.then === 'function'; } // isIterator(iter) function isIterator(iter) { return typeof iter === 'object' && !!iter && (typeof iter.next === 'function' || isIterable(iter)); } // isIterable(iter) function isIterable(iter) { return typeof iter === 'object' && !!iter && typeof Symbol === 'function' && !!Symbol.iterator && typeof iter[Symbol.iterator] === 'function'; } // makeArrayFromIterator(iter or array) function makeArrayFromIterator(iter) { if (iter instanceof Array) return iter; if (!isIterator(iter)) return [iter]; if (isIterable(iter)) iter = iter[Symbol.iterator](); var array = []; try { for (;;) { var val = iter.next(); if (val && val.hasOwnProperty('done') && val.done) return array; if (val && val.hasOwnProperty('value')) val = val.value; array.push(val); } } catch (error) { return array; } } // makeArrayFromIterator // promisify(fn, [options]) function promisify(fn, options) { // promisify(object target, string method, [object options]) : undefined if (fn && typeof fn === 'object' && options && typeof options === 'string') { var object = fn, method = options, options = arguments[2]; var suffix = options && typeof options === 'string' ? options : options && typeof options.suffix === 'string' ? options.suffix : options && typeof options.postfix === 'string' ? options.postfix : 'Async'; var methodAsyncCached = method + suffix + 'Cached'; Object.defineProperty(object, method + suffix, { get: function () { return this.hasOwnProperty(methodAsyncCached) && typeof this[methodAsyncCached] === 'function' ? this[methodAsyncCached] : (setValue(this, methodAsyncCached, promisify(this, this[method])), this[methodAsyncCached]); }, configurable: true }); return; } // promisify([object ctx,] function fn) : function var ctx = typeof this !== 'function' ? this : undefined; if (typeof options === 'function') ctx = fn, fn = options, options = arguments[2]; if (options && options.context) ctx = options.context; if (typeof fn !== 'function') throw new TypeError('promisify: argument must be a function'); // promisified promisified.promisified = true; return promisified; function promisified() { var args = arguments; return new Promise(function (res, rej) { args[args.length++] = function callback(err, val) { try { return err instanceof Error ? rej(err) : // normal node style callback arguments.length === 2 ? (err ? rej(err) : res(val)) : // fs.exists like callback, arguments[0] is value arguments.length === 1 ? res(arguments[0]) : // unknown callback arguments.length === 0 ? res() : // child_process.exec like callback res(slice.call(arguments, err == null ? 1 : 0)); } catch (e) { rej(e); } }; fn.apply(ctx, args); }); }; } // promisify // thunkify(fn, [options]) function thunkify(fn, options) { // thunkify(object target, string method, [object options]) : undefined if (fn && typeof fn === 'object' && options && typeof options === 'string') { var object = fn, method = options, options = arguments[2]; var suffix = options && typeof options === 'string' ? options : options && typeof options.suffix === 'string' ? options.suffix : options && typeof options.postfix === 'string' ? options.postfix : 'Async'; var methodAsyncCached = method + suffix + 'Cached'; Object.defineProperty(object, method + suffix, { get: function () { return this.hasOwnProperty(methodAsyncCached) && typeof this[methodAsyncCached] === 'function' ? this[methodAsyncCached] : (setValue(this, methodAsyncCached, thunkify(this, this[method])), this[methodAsyncCached]); }, configurable: true }); return; } // thunkify([object ctx,] function fn) : function var ctx = typeof this !== 'function' ? this : undefined; if (typeof options === 'function') ctx = fn, fn = options, options = arguments[2]; if (options && options.context) ctx = options.context; if (typeof fn !== 'function') throw new TypeError('thunkify: argument must be a function'); // thunkified thunkified.thunkified = true; return thunkified; function thunkified() { var result, callbacks = [], unhandled; arguments[arguments.length++] = function callback(err, val) { if (result) { if (err) hasConsoleError && console.error(COLOR_ERROR + 'Unhandled callback error: ' + err2str(err) + COLOR_NORMAL); return; } result = arguments; if (callbacks.length === 0 && err instanceof Error) unhandled = true, hasConsoleError && console.error(COLOR_ERROR + 'Unhandled callback error: ' + err2str(err) + COLOR_NORMAL); for (var i = 0, n = callbacks.length; i < n; ++i) fire(callbacks[i]); callbacks = []; }; fn.apply(ctx, arguments); // thunk return function thunk(cb) { if (typeof cb !== 'function') throw new TypeError('argument must be a function'); if (unhandled) unhandled = false, hasConsoleError && console.error(COLOR_ERROR + 'Unhandled callback error handled: ' + err2str(result[0]) + COLOR_NORMAL); if (result) return fire(cb); callbacks.push(cb); }; // fire function fire(cb) { var err = result[0], val = result[1]; try { return err instanceof Error || result.length === cb.length ? cb.apply(ctx, result) : // normal node style callback result.length === 2 ? cb.call(ctx, err, val) : // fs.exists like callback, arguments[0] is value result.length === 1 ? cb.call(ctx, null, result[0]) : // unknown callback result.length === 0 ? cb.call(ctx) : // child_process.exec like callback cb.call(ctx, null, slice.call(result, err == null ? 1 : 0)); } catch (e) { cb.call(ctx, e); } } // fire }; // thunkified } // thunkify // promisifyAll(object, options) function promisifyAll(object, options) { var keys = []; if (Object.getOwnPropertyNames) keys = Object.getOwnPropertyNames(object); else if (Object.keys) keys = Object.keys(object); else for (var method in object) if (object.hasOwnProperty(method)) keys.push(i); keys.forEach(function (method) { if (typeof object[method] === 'function' && !object[method].promisified && !object[method].thunkified) promisify(object, method, options); }); return object; } // thunkifyAll(object, options) function thunkifyAll(object, options) { var keys = []; if (Object.getOwnPropertyNames) keys = Object.getOwnPropertyNames(object); else if (Object.keys) keys = Object.keys(object); else for (var method in object) if (object.hasOwnProperty(method)) keys.push(i); keys.forEach(function (method) { if (typeof object[method] === 'function' && !object[method].promisified && !object[method].thunkified) thunkify(object, method, options); }); return object; } /* var p1 = Promise.reject(new Error); setTimeout(function () { p1.catch(function () {}); }, 1); var p2 = Promise.reject(new Error).then(function () {}); setTimeout(function () { p2.catch(function () {}); }, 1); */ function err2str(err) { return err.stack || (err + ''); } if (!global.Promise) global.Promise = Promise; if (!global.PromiseThunk) global.PromiseThunk = Promise; setValue(Promise, 'Promise', Promise); setValue(Promise, 'PromiseThunk', Promise); if (typeof module === 'object' && module && module.exports) module.exports = Promise; return Promise; }(Function('return this')(), typeof Promise === 'function' ? Promise : null);