Skip to content

Instantly share code, notes, and snippets.

@davidbarral
Last active March 5, 2018 08:44
Show Gist options
  • Save davidbarral/a51029666c4c5ba861010db0fc56a845 to your computer and use it in GitHub Desktop.
Save davidbarral/a51029666c4c5ba861010db0fc56a845 to your computer and use it in GitHub Desktop.
Promises
// 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);
});
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