Last active
August 29, 2015 14:16
-
-
Save bellbind/7a10bed54d6033631c4d to your computer and use it in GitHub Desktop.
[es6][ecmascript][iojs]Tiny Implementation for ES6 Promise API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var TinyPromise = require("./promise"); | |
//var TinyPromise = Promise; | |
// utilities | |
var wait = function (ms) { | |
return function waiting(v) { | |
return new TinyPromise(function (f, r) {setTimeout(f, ms, v);}); | |
}; | |
}; | |
var timeout = function (ms) { | |
return new TinyPromise(function (f, r) {setTimeout(r, ms, ms);}); | |
}; | |
// examples | |
console.log(TinyPromise.resolve()); | |
// nextTick order would be: 1 => 2 => 3 => 4 | |
TinyPromise.resolve().then(function () {console.log(2); return 2;}) | |
.then(function (v) {console.log(v + 2);}); | |
TinyPromise.resolve().then(function () {console.log(3);}); | |
console.log(1); | |
// mixed array for all | |
TinyPromise.all([ | |
1, 2, TinyPromise.resolve(3), new TinyPromise(function (f, r) { | |
f(2 * 2); | |
}), | |
]).then(function (r) {console.log(r)}); | |
// fake thenable | |
TinyPromise.resolve({ | |
then: function (p, t) { | |
throw new Error("abc"); | |
}, | |
}).catch(function (e) {console.log(e);}); | |
TinyPromise.resolve({then: 10}).then(function (v) {console.log(v);}); | |
// cancel rejected | |
TinyPromise.reject(Error("xxx")).catch(function (e) { | |
return e.message; | |
}).then(function (m) {console.log(m);}); | |
// error in resolver | |
new TinyPromise(function (f, r) { | |
throw new Error("DEF"); | |
}).catch(function (e) {console.log(e);}); | |
// then(null)/then() carry to next then | |
TinyPromise.resolve(100).then(null).then().then(console.log.bind(console)); | |
// then(throwError, uncaughted) | |
TinyPromise.resolve(10).then(function (v) { | |
throw new Error("error"); | |
}, function (err) { | |
console.log("uncauted", err); | |
}).then(null, function (err) { | |
console.log("catched uncauted", err); | |
}); | |
// wait promise | |
TinyPromise.race([ | |
wait(50)(TinyPromise.resolve(20)), timeout(100) | |
]).then(function (v) { | |
console.log("success", v); | |
}, function (e) { | |
console.log("timeout", e); | |
}); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Tiny Promise on Browser</title> | |
<script src="promise.js"></script> | |
</head> | |
<body> | |
<button onclick=' | |
new TinyPromise(function (f, r) { | |
var d = new Date(); | |
setTimeout(function () { | |
f(d); | |
}, 1000); | |
}).then(function (v) { | |
var ul = document.getElementById("log"); | |
var li = document.createElement("li"); | |
li.textContent = v.toString() + " => " + new Date(); | |
ul.insertBefore(li, ul.firstChild); | |
}); | |
'>add an item after 1sec</button> | |
<ul id="log"></ul> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Tiny implementation of ES6 Promise API | |
// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects | |
(function (ctx) { | |
"use strict"; | |
// Engine dependent functions | |
var engine = (function () { | |
// fallback default polyfills for ES5 | |
var engine = { | |
// mimic weak ref inspired by Blitz-Collections WeakMap | |
// hide to set["__conn__"] in the descriptor of obj["constructor"] | |
// change only result of Object.getOwnPropertyNames(promise) | |
set: function (promise, connector) { | |
var ctor = promise.constructor; | |
var setter = function (c) {ctor = c;}; | |
setter.__conn__ = connector; | |
Object.defineProperty(promise, "constructor", { | |
get: function () {return ctor;}, set: setter, | |
}); | |
}, | |
get: function (promise) { | |
return Object.getOwnPropertyDescriptor( | |
promise, "constructor").set.__conn__; | |
}, | |
setImmediate: function (f) {setTimeout(f, 0);}, | |
from: (function () {// Array.from(arraylike, mapfunc) | |
try { | |
return new Function( | |
"a", | |
["var M = arguments[1], T = arguments[2];", | |
"var r = [], i = 0, e;", | |
"if (M) for (e of a) r.push(M.call(T, e, i++, a));", | |
"else for (e of a) r.push(e);", | |
"return r;"].join("\n") | |
); | |
} catch (e) { | |
// no for-of loop engine (iterator not supported) | |
return function from(a) { | |
var M = arguments[1], T = arguments[2], | |
r = [].slice.call(a); | |
return M ? r.map(M, T) : r; | |
} | |
} | |
})(), | |
}; | |
if (typeof WeakMap === "function") { | |
// Use WeakMap for hiding Connector prop of Promise from outside | |
var priv = new WeakMap(); | |
engine.set = priv.set.bind(priv); | |
engine.get = priv.get.bind(priv); | |
} | |
if (typeof setImmediate === "function") { | |
engine.setImmediate = setImmediate; | |
} | |
if (Array.from) engine.from = Array.from.bind(Array); | |
return engine; | |
})(); | |
// Internal Connector: propagate from "resolver" to "then" Promises | |
var Connector = function Connector() { | |
this.resolved = null; // object has "value" or "error" | |
this.nexts = []; // turns null after resolved/rejected for judge | |
}; | |
Connector.prototype.pushNext = function (fulfill, reject) { | |
if (this.nexts) { | |
this.nexts.push({fulfill: fulfill, reject: reject}); | |
} else if (this.resolved.hasOwnProperty("value")) { | |
fulfill(this.resolved.value); | |
} else { | |
reject(this.resolved.error); | |
} | |
}; | |
Connector.prototype.fulfill = function (value) { | |
if (!this.nexts) return; | |
if (value instanceof Promise) { | |
value.then(this.doFulfill.bind(this), this.doReject.bind(this)); | |
} else if (value && value.then instanceof Function) { | |
// thenable start new task by es6-draft 25.4.2.2 | |
new Promise(function (f, r) {value.then(f, r);}) | |
.then(this.doFulfill.bind(this), this.doReject.bind(this)); | |
} else { | |
this.doFulfill(value); | |
} | |
}; | |
Connector.prototype.reject = function (error) { | |
if (!this.nexts) return; | |
this.doReject(error); | |
}; | |
Connector.prototype.doFulfill = function (value) { | |
this.nexts.forEach(function (next) { | |
next.fulfill(value); | |
}); | |
this.resolved = {value: value}; | |
this.nexts = null; | |
}; | |
Connector.prototype.doReject = function (error) { | |
this.nexts.forEach(function (next) { | |
next.reject(error); | |
}); | |
this.resolved = {error: error}; | |
this.nexts = null; | |
}; | |
// ES6 Promise API | |
var Promise = function Promise(resolver) { | |
var connector = new Connector(); | |
engine.set(this, connector); | |
try { | |
resolver(function (value) { | |
engine.setImmediate(connector.fulfill.bind(connector, value)); | |
}, function (error) { | |
engine.setImmediate(connector.reject.bind(connector, error)); | |
}); | |
} catch (error) { | |
engine.setImmediate(connector.reject.bind(connector, error)); | |
}; | |
}; | |
Promise.prototype.then = function then(onFulfill, onReject) { | |
var self = this; | |
return new Promise(function (nextFul, nextRej) { | |
var connectFulfill = onFulfill ? function (value) { | |
try { | |
nextFul(onFulfill(value)); | |
} catch (e) {nextRej(e);} | |
} : nextFul; | |
var connectReject = onReject ? function (error) { | |
try { | |
nextFul(onReject(error)); | |
} catch (e) {nextRej(e);} | |
} : nextRej; | |
engine.get(self).pushNext(connectFulfill, connectReject); | |
}); | |
}; | |
Promise.prototype.catch = function (reject) { | |
return this.then(null, reject); | |
}; | |
Promise.resolve = function resolve(value) { | |
return new Promise(function (fulfill, reject) {fulfill(value);}); | |
}; | |
Promise.reject = function reject(value) { | |
return new Promise(function (fulfill, reject) {reject(value);}); | |
}; | |
Promise.race = function race(promises) { | |
return new Promise(function (fulfill, reject) { | |
engine.from(promises, function (p) { | |
Promise.resolve(p).then(fulfill, reject); | |
}); | |
}); | |
}; | |
Promise.all = function all(promises) { | |
return new Promise(function (fulfill, reject) { | |
var c = 0; | |
var r = engine.from(promises, function (p, i) { | |
Promise.resolve(p).then(function (v) { | |
r[i] = v; | |
c++; | |
if (c === l) fulfill(r); | |
}, reject); | |
}); | |
var l = r.length; | |
}); | |
}; | |
if (typeof module !== "undefined") { | |
module.exports = Promise; | |
} else { | |
ctx.TinyPromise = Promise; | |
} | |
})(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
NOTE: Commons/Promises/A and Q interface Promise implementation(Sep. 2012):