Last active
July 25, 2023 05:42
-
-
Save ronkot/5ca27289a3795651f19f78c2b1f7ab16 to your computer and use it in GitHub Desktop.
Simple Promise implementation
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
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); | |
} | |
}); | |
} | |
} |
@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))
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)
})
}
Thanks @Jarosic, fixed that 👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple and brilliant!
Could you explain the use case of
Promise.fromNode
?