Last active
March 5, 2018 08:44
-
-
Save davidbarral/a51029666c4c5ba861010db0fc56a845 to your computer and use it in GitHub Desktop.
Promises
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
// Same as promise.js but with debug messages. | |
// Just run the code with node promise-debug.js. | |
// By setting DEBUG to true each promise will output in console some debug info | |
// There are 13 samples. In terms of debug output is better to focus in one of them at a time. | |
// You can do that by setting the FOCUS variable (i.e. FOCUS=11) | |
const DEBUG = false; | |
const FOCUS = false; | |
let promiseId = 1; | |
function Promise(fn = () => {}) { | |
let status = "PENDING"; | |
let value; | |
let fullfillHandlers = []; | |
const _id = promiseId++; | |
const _debug = (...args) => DEBUG && console.log(`[${_id}](${status},${value})`, ...args); | |
const thenable = obj => Boolean(obj && obj.then); | |
const fullfill = newStatus => newValue => { | |
status = newStatus; | |
value = newValue; | |
_debug("fullfilled!"); | |
fullfillHandlers.forEach(handler => handler(status, value)); | |
}; | |
const onFullfill = handler => { | |
if (status === "PENDING") { | |
_debug("attach onFullfillHandler"); | |
fullfillHandlers.push(handler); | |
} else { | |
_debug("execute onFullfillHandler"); | |
handler(status, value); | |
} | |
} | |
const resolve = fullfill("RESOLVED"); | |
const reject = fullfill("REJECTED"); | |
setImmediate(() => { | |
_debug("execute"); | |
try { | |
fn(resolve, reject); | |
} catch(e) { | |
_debug("execute failed"); | |
reject(e); | |
} | |
}); | |
_debug("created!"); | |
return { | |
then(handler) { | |
_debug(`attach then -> [${promiseId}]`); | |
return new Promise((resolve, reject) => { | |
onFullfill((status, value) => { | |
if (status === "REJECTED") { | |
_debug("flow reject"); | |
reject(value); | |
return; | |
} | |
try { | |
_debug("execute then handler"); | |
const res = handler(value); | |
_debug("thenable?", thenable(res), res); | |
if (thenable(res)) { | |
res.then(resolve).catch(reject); | |
} else { | |
resolve(res); | |
} | |
} catch(e) { | |
_debug("execute then handler failed"); | |
reject(e); | |
} | |
}); | |
}); | |
}, | |
catch(handler) { | |
_debug(`attach catch -> [${promiseId}])`,); | |
return new Promise((resolve, reject) => { | |
onFullfill((status, value) => { | |
if (status === "RESOLVED") { | |
_debug("flow resolve"); | |
resolve(value); | |
return; | |
} | |
try { | |
_debug("execute catch handler"); | |
const res = handler(value); | |
_debug("thenable?", thenable(res)); | |
if (thenable(res)) { | |
res.then(resolve).catch(reject); | |
} else { | |
resolve(res); | |
} | |
} catch(e) { | |
_debug("execute catch handler failed"); | |
reject(e); | |
} | |
}); | |
}); | |
} | |
}; | |
}; | |
// ------------------------------------------- | |
// | |
// S A M P L E S | |
// | |
// ------------------------------------------- | |
const sample = (id, cb) => { | |
const ok = (...args) => console.log(`<SAMPLE ${id}> OK`, ...args); | |
const ko = (...args) => console.log(`<SAMPLE ${id}> KO`, ...args); | |
const fail = (reason = "") => () => { | |
throw `FAIL ${reason}`; | |
} | |
if (!FOCUS || FOCUS === id) { | |
cb(ok, ko, fail); | |
} | |
} | |
sample(1, () => { | |
new Promise() | |
}); | |
sample(2, () => { | |
new Promise(resolve => resolve(1)) | |
}); | |
sample(3, () => { | |
new Promise((_, reject) => reject(2)); | |
}); | |
sample(4, (ok, ko) => { | |
const promise = new Promise(resolve => resolve("resolve")); | |
promise.then(ok); | |
promise.then(ok); | |
}); | |
sample(5, (ok, ko) => { | |
promise = new Promise((_, reject) => reject("reject")); | |
promise.then(ok); | |
promise.catch(ko); | |
promise.catch(ko); | |
}); | |
sample(6, (ok, ko, fail) => { | |
promise = new Promise(fail()); | |
promise.then(ok); | |
promise.catch(ko); | |
}); | |
sample(7, (ok) => { | |
new Promise(resolve => resolve("resolve")) | |
.then(t => `${t} - then1`) | |
.then(t => `${t} - then2`) | |
.then(ok); | |
}); | |
sample(8, (ok) => { | |
new Promise((_, reject) => reject("reject")) | |
.then(t => `${t} - then`) | |
.catch(t => `${t} - catch`) | |
.then(ok); | |
}); | |
sample(9, (ok, ko, fail) => { | |
new Promise(resolve => resolve("resolve")) | |
.then(t => Promise.resolve(`${t} - then (promise1)`)) | |
.then(t => Promise.resolve(`${t} - then (promise2)`) | |
.then(t => `${t} - then (nested promise)`) | |
) | |
.then(t => `${t} - then`) | |
.then(ok) | |
}); | |
sample(10, (ok) => { | |
Promise.resolve("resolved").then(ok); | |
}); | |
sample(11, (ko) => { | |
Promise.reject("rejected").catch(ko); | |
}); | |
sample(12, (ok, ko) => { | |
Promise.all([ | |
Promise.resolve("resolved 1"), | |
Promise.resolve("resolved 2"), | |
]).then(ok); | |
Promise.all([ | |
Promise.resolve("resolved 1"), | |
Promise.reject("rejected"), | |
]).catch(ko); | |
}); | |
sample(13, (ok, ko) => { | |
Promise.race([ | |
Promise.resolve("resolved 1"), | |
Promise.resolve("resolved 2"), | |
]).then(ok); | |
Promise.race([ | |
Promise.resolve("resolved 1"), | |
Promise.reject("rejected"), | |
]).then(ok); | |
Promise.race([ | |
Promise.reject("rejected"), | |
Promise.resolve("resolved 1"), | |
]).catch(ko); | |
}); |
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
function Promise(fn = () => {}) { | |
let status = "PENDING"; | |
let value; | |
let fullfillHandlers = []; | |
const thenable = obj => Boolean(obj && obj.then); | |
const fullfill = newStatus => newValue => { | |
status = newStatus; | |
value = newValue; | |
fullfillHandlers.forEach(handler => handler(status, value)); | |
}; | |
const onFullfill = handler => { | |
if (status === "PENDING") { | |
fullfillHandlers.push(handler); | |
} else { | |
handler(status, value); | |
} | |
} | |
const resolve = fullfill("RESOLVED"); | |
const reject = fullfill("REJECTED"); | |
setImmediate(() => { | |
try { | |
fn(resolve, reject); | |
} catch(e) { | |
reject(e); | |
} | |
}); | |
return { | |
then(handler) { | |
return new Promise((resolve, reject) => { | |
onFullfill((status, value) => { | |
if (status === "REJECTED") { | |
reject(value); | |
return; | |
} | |
try { | |
const res = handler(value); | |
if (thenable(res)) { | |
res.then(resolve).catch(reject); | |
} else { | |
resolve(res); | |
} | |
} catch(e) { | |
reject(e); | |
} | |
}); | |
}); | |
}, | |
catch(handler) { | |
return new Promise((resolve, reject) => { | |
onFullfill((status, value) => { | |
if (status === "RESOLVED") { | |
resolve(value); | |
return; | |
} | |
try { | |
const res = handler(value); | |
if (thenable(res)) { | |
res.then(resolve).catch(reject); | |
} else { | |
resolve(res); | |
} | |
} catch(e) { | |
reject(e); | |
} | |
}); | |
}); | |
} | |
}; | |
}; | |
Promise.resolve = v => new Promise(resolve => resolve(v)); | |
Promise.reject = v => new Promise((_, reject) => reject(v)); | |
Promise.all = (promises) => new Promise((resolve, reject) => { | |
let resolved = 0; | |
const values = Array(promises.length).fill(undefined); | |
promises.forEach((promise, i) => { | |
promise | |
.then(v => { | |
values[i] = v; | |
resolved++; | |
if (resolved === values.length) { | |
resolve(values); | |
} | |
}) | |
.catch(e => reject(e)); | |
}); | |
}); | |
Promise.race = (promises) => new Promise((resolve, reject) => { | |
let resolved = false; | |
promises.forEach((promise, i) => { | |
promise | |
.then(v => { | |
if (resolved) { | |
return; | |
} | |
resolved = true; | |
resolve(v); | |
}) | |
.catch(e => { | |
if (!resolved) { | |
reject(e) | |
} | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment