Skip to content

Instantly share code, notes, and snippets.

@lqt0223
Last active March 9, 2018 04:18
Show Gist options
  • Save lqt0223/a2169956dc329af7fbb6a63196356073 to your computer and use it in GitHub Desktop.
Save lqt0223/a2169956dc329af7fbb6a63196356073 to your computer and use it in GitHub Desktop.
26 Another implementation of Promise
// This gist shows implementation of the Promise class in JavaScript. The implementation includes:
// 1. **basic**: a promise is an object that receives a function as parameter. When created, the function will be fired immediately
// 2. **job scheduling**: a promise is 'Thenable' and can call 'Promise.prototype.then' to schedule deferred jobs. When the promise is resolved, the callback in 'then' body will be called and the resolved value will be retrieved
// 3. **chaining**: a 'then' body will return a new Promise, which is also 'Thenable'. Once the first promise is fired, the promise chain will do the resolution towards its end automatically
// 4. **status control**: a promise has a initial state of 'pending', and will shift either to 'resolved' or 'rejected'
// 5. **error handling**: Promise.prototype.then' with error handler & Promise.prototype.catch' are ways to handle errors
// 6. **error propagation**: a rejected reason will be propagated to the nearest error handler or catch body. Jobs between the rejection and the nearest error handler would not be executed. After the error handling, the promise chain resumes execution
// 7. **auto-resolution of promise in then / catch body**: a promise returned from the callback in either a then or catch body will be automatically resolved or rejected, and the resolved value or rejected reason will appear in the next chained promise body
// 8. **exception capture**: besides explicit rejection, a promise can capture a thrown error or exception in its body
// 9. **static methods**: "Promise.resolve", "Promise.reject", "Promise.all" & "Promise.reject"
// 10. **re-thenablilty**: by calling 'Promise.prototype.then' on a promise multiple times, concurrent async jobs are scheduled
class Promise {
constructor(handler) {
const ref = this
this.status = 'pending'
this.next = []
// a util function to callback the resolved value of a promise
// the function will modify promise chain to avoid duplicate job scheduling
const _resolve = (p, ref, cb) => {
p.then((value) => {
cb(value)
})
p.next = ref.next
}
// determine of the second value is an immediate value or a promise
// do the sync or async resolution and try to resolve next
const _resolveNext = (ref, nValue) => {
if (ref.next) {
if (!(nValue && nValue.constructor && nValue.constructor.name == 'Promise')) {
resolveNext(ref.next, nValue)
} else {
_resolve(nValue, ref, (nnValue) => {
resolveNext(ref.next, nnValue)
})
}
}
}
// propagate through the promise chain, by invoking the deferred function in sequence
const resolveNext = (ref, value) => {
ref.status = 'resolved'
if (ref.handler) {
try {
var nValue = ref.handler(value)
_resolveNext(ref, nValue)
} catch (e) {
rejectNext(ref, e)
}
}
}
// propagate a error reason through the promise chain, until the first errorHandler is found
// handle the error there and resume promise chain from there
const rejectNext = (ref, reason) => {
if (!ref) {
console.log('UnhandledPromiseRejectionWarning:', reason)
} else {
ref.status = 'rejected'
if (ref.errorHandler) {
try {
var nValue = ref.errorHandler(reason)
_resolveNext(ref, nValue)
} catch (e) {
rejectNext(ref.next, e)
}
} else {
rejectNext(ref.next, reason)
}
}
}
const resolve = (value) => {
ref.status = 'resolved'
if (ref.next && ref.next.length > 0) {
ref.next.forEach((promise) => {
resolveNext(promise, value)
})
}
}
const reject = (reason) => {
ref.status = 'rejected'
if (ref.next) {
rejectNext(ref.next, reason)
}
}
// if the promise constructor is called with a function, execute 'immediately'
if (handler && handler.constructor && handler.constructor.name == 'Function') {
setTimeout(() => {
try {
handler(resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
}
then(handler, errorHandler) {
var p = new Promise()
if (handler) {
p.handler = handler
}
if (errorHandler) {
p.errorHandler = errorHandler
}
this.next.push(p)
return p
}
// generate a new promise with the resolve handler that will simply propagate the result
catch(errorHandler) {
return this.then((value) => {
return value
}, errorHandler)
}
static resolve(value) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(value)
}, 0)
})
}
static reject(value) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(value)
}, 0)
})
}
static all(promises) {
var len = promises.length
var resolved = 0
var values = new Array(len)
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
if (!(promise && promise.constructor && promise.constructor.name == 'Promise')) {
promise = Promise.resolve(promise)
}
promise.then((value) => {
values[index] = value
resolved++
if (resolved == len) {
resolve(values)
}
}).catch((e) => {
reject(e)
})
})
})
}
static race(promises) {
var resolved = 0
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
if (!(promise && promise.constructor && promise.constructor.name == 'Promise')) {
promise = Promise.resolve(promise)
}
promise.then((value) => {
resolved++
if (resolved == 1) {
resolve(value)
}
}).catch((e) => {
reject(e)
})
})
})
}
}
module.exports = Promise
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment