Last active
May 19, 2018 18:56
-
-
Save Yengas/64d9f35f95dcb7992ada338a4cbefc8a to your computer and use it in GitHub Desktop.
A promise implementation without any spec compliance. Just for fun.
This file contains hidden or 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
| class MyPromise{ | |
| // create a new instance of a promise | |
| // callback is a function that accepts resolve and reject functions as params | |
| constructor(callback){ | |
| this.resolved = false; | |
| this.reject = false; | |
| this.value = null; | |
| // to temporarily hold promises that got created while this promise was in pending state | |
| this.waiting = []; | |
| try{ | |
| callback( | |
| // the resolve function calls on finish with the first param true | |
| (value) => this.onFinish(true, value), | |
| // catch with the second param as false | |
| (value) => this.onFinish(false, value), | |
| ); | |
| }catch(e){ | |
| // reject in case the callback throws an error | |
| this.onFinish(false, e); | |
| } | |
| } | |
| // resolve/reject this promise | |
| onFinish(resolved, value){ | |
| // we don't want to resolve already resolved/rejected promises. | |
| if(this.resolved || this.reject) return null; | |
| // set the state of the promise | |
| if(resolved) this.resolved = true; | |
| else this.reject = true; | |
| // hold the resolved value | |
| this.value = value; | |
| // which function to call in the waiting list | |
| const idx = this.resolved ? 0 : 1; | |
| // either call all resolves or rejects from the waiting arr. | |
| this.waiting.forEach((arr) => arr[idx](value)); | |
| // we don't need to hold the waiting values for this promise anymore, since it resolved. | |
| this.waiting = null; | |
| } | |
| // call/store resolve/reject methods of newly created promises | |
| process(onResolve, onReject){ | |
| // if this promise is resolved or rejected, just call the proper method with the stored value | |
| if(this.resolved || this.reject) | |
| return this.resolved ? onResolve(this.value) : onReject(this.value); | |
| // otherwise, we need to wait until the promise resolves. | |
| this.waiting.push([onResolve, onReject]); | |
| } | |
| // given a value, and a mapper function, call resolve/reject methods safely. | |
| wrapCallSafe(value, mapper, resolve, reject){ | |
| try{ | |
| const result = mapper(value); | |
| // if the result is promise, wait for it resolve (unwrapping) | |
| if(result && result.constructor == MyPromise){ | |
| result.then(resolve).catch(reject); | |
| }else{ | |
| // else just resolve with the result | |
| resolve(result); | |
| } | |
| }catch(e){ | |
| // if any errors occur in the mapper func, call reject | |
| reject(e); | |
| } | |
| }; | |
| createNewPromise(isThen, mapper){ | |
| return new MyPromise((resolve, reject) => { | |
| // create a onResolve/onReject functions that safely calls resolve/reject of our promise library, by mapping the value | |
| const onResolve = (value) => { | |
| // if the promise is created with .then call, we need to map the value before calling resolve | |
| this.wrapCallSafe(value, isThen ? mapper : id, resolve, reject); | |
| } | |
| const onReject = (value) => { | |
| // if the promise is created with .catch call, we need to map the value before calling reject/resolve | |
| // in case onReject was called, on a Promise we created with .then, we need to reject. | |
| this.wrapCallSafe(value, isThen ? id : mapper, isThen ? reject : resolve, reject); | |
| } | |
| // call or store our onResolve/onReject methods according to promise state | |
| this.process(onResolve, onReject); | |
| }); | |
| } | |
| then(func, func2){ | |
| const newPromise = this.createNewPromise(true, func); | |
| return func2 ? newPromise.catch(func2) : newPromise; | |
| } | |
| catch(func){ | |
| return this.createNewPromise(false, func); | |
| } | |
| } | |
| function id(x){ return x; }; | |
| // resolved unit function for promise | |
| function resolve(value){ | |
| if(value.constructor == MyPromise){ | |
| return new MyPromise((resolve) => value.then(resolve).catch(resolve)); | |
| } | |
| return new MyPromise((resolve) => resolve(value)); | |
| } | |
| // reject unit function for promise | |
| function reject(err){ | |
| if(err.constructor == MyPromise){ | |
| return new MyPromise((_, reject) => err.then(reject).catch(reject)); | |
| } | |
| return new MyPromise((_, reject) => reject(err)); | |
| } | |
| async function main(){ | |
| const test1 = new MyPromise((resolve, reject) => setTimeout(resolve, 1000, 5)) | |
| .then((x) => { throw new Error('asd'); }) | |
| .catch((x) => 10) | |
| .catch((y) => 15) | |
| .then((x) => resolve(resolve(x + 10))); | |
| console.log('wtf'); | |
| console.log(await test1); // await works correctly... | |
| console.log('waiting for reject...'); | |
| await reject(new Error('yo!')); // this register on .then aswell | |
| } | |
| main().catch((e) => console.log('caught', e)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment