Skip to content

Instantly share code, notes, and snippets.

@ronkot
Last active July 25, 2023 05:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ronkot/5ca27289a3795651f19f78c2b1f7ab16 to your computer and use it in GitHub Desktop.
Save ronkot/5ca27289a3795651f19f78c2b1f7ab16 to your computer and use it in GitHub Desktop.
Simple Promise implementation
const PENDING = 1;
const RESOLVED = 2;
const REJECTED = 3;
const callLater = (fn) => setTimeout(fn, 0);
class Promise {
constructor(initPromiseFn) {
this._state = PENDING;
this._value = undefined;
this._callbacks = [];
this._resolve = this._resolve.bind(this);
this._reject = this._reject.bind(this);
initPromiseFn(this._resolve, this._reject);
}
/* Helper methods */
_resolve(value) {
if (this._state === PENDING) {
this._state = RESOLVED;
this._value = value;
while (this._callbacks.length > 0) {
this._handleCallback(this._callbacks.pop());
}
}
}
_reject(value) {
if (this._state === PENDING) {
this._state = REJECTED;
this._value = value;
while (this._callbacks.length > 0) {
this._handleCallback(this._callbacks.pop());
}
}
}
_handleCallback(callback) {
if (this._state === RESOLVED) {
callback.onResolved && callLater(() => callback.onResolved(this._value));
} else if (this._state === REJECTED) {
callback.onRejected && callLater(() => callback.onRejected(this._value));
} else {
this._callbacks.push(callback);
}
}
/* Public methods */
then(onResolved, onRejected) {
return new Promise((resolve, reject) => {
const callback = {
onResolved: (value) => {
let nextValue = value;
if (onResolved) {
try {
nextValue = onResolved(value);
if (nextValue && nextValue.then) {
return nextValue.then(resolve, reject);
}
} catch (err) {
return reject(err);
}
}
resolve(nextValue);
},
onRejected: (value) => {
let nextValue = value;
if (onRejected) {
try {
nextValue = onRejected(value);
if (nextValue && nextValue.then) {
return nextValue.then(resolve, reject);
}
} catch (err) {
return reject(err);
}
}
reject(nextValue);
},
};
this._handleCallback(callback);
});
}
done(onResolved) {
return this.then(onResolved);
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
/* Public static tools */
static resolve(value) {
return new Promise((resolve) => resolve(value));
}
static reject(value) {
return new Promise((resolve, reject) => reject(value));
}
static delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
static fromNode(fn) {
return new Promise((resolve, reject) => {
const resolveNode = (err, res) => {
if (err) return reject(err);
resolve(res);
};
fn(resolveNode);
});
}
static promisify(nodeFn) {
return (...args) => Promise.fromNode((resolveFn) => nodeFn(...args, resolveFn));
}
static promisifyAll(module) {
Object.keys(module)
.filter((key) => typeof module[key] === 'function' && !key.endsWith('Sync'))
.forEach((key) => (module[`${key}Async`] = Promise.promisify(module[key])));
return module;
}
static all(promises) {
return new Promise((resolve, reject) => {
const values = new Array(promises.length);
let counter = 0;
const tryResolve = (i) => (value) => {
values[i] = value;
counter++;
if (counter === promises.length) {
resolve(values);
}
};
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
promise.then(tryResolve(i), reject);
}
});
}
}
@ronkot
Copy link
Author

ronkot commented Nov 13, 2017

This was written during my presentation DIY Promise at Wunderdog.

@qetr1ck-op
Copy link

qetr1ck-op commented Jul 16, 2018

Simple and brilliant!
Could you explain the use case of Promise.fromNode?

@ronkot
Copy link
Author

ronkot commented Jul 20, 2018

@qetr1ck-op thanks!

Promise.fromNode is a great utility to "promisify" Node's callback -style function on the fly. For example, if I'd like to read some file using fs.readFile:

const fs = require('fs')

Promise.fromNode(callback => fs.readFile('some-text', 'utf8', callback))
.then(text => console.log(text))
.catch(err => console.error(err))

@Jarosic
Copy link

Jarosic commented Jan 18, 2019

You have mistake in this block

onRejected: (value) => {
let nextValue = value
if (onRejected) {
try {
nextValue = onRejected(value)
if (nextValue && nextValue.then) {
return nextValue.then(resolve, reject)
}
} catch (err) {
return reject(err)
}
}
resolve(nextValue) // you should use reject(nextValue) here
}
}
this._handleThenner(thenner)
})
}

@ronkot
Copy link
Author

ronkot commented Dec 31, 2019

Thanks @Jarosic, fixed that 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment