Last active
February 27, 2016 18:00
-
-
Save baygeldin/1c3836108bd0d41d244c to your computer and use it in GitHub Desktop.
Guide to the implementation of promises.
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
#!usr/env/bin node | |
'use strict'; | |
// Disclaimer | |
// The most basic promises implementation. | |
// I needed to do it to wrap my head around them. | |
// Another reason to do it was to get myself | |
// familiar with some code conventions, so | |
// sorry for ES5 :D | |
// Guide | |
// When you want to implement promises, do it | |
// incrementally. Step-by-step: | |
// 1) Implement it as state machine, without any | |
// methods! All it needs to do is to change it's | |
// state, when the promise is done. | |
// 2) Implement .done method. It's pretty easy, isn't it? | |
// It needs a handle private method in turn. All it does is | |
// pushing a listener (callback) to an array of listeners, or | |
// executes it immidiately, if promise is already done. | |
// 3) Implement .then method. It should return a new Promise, | |
// which is resolved or rejected EXACTLY when our promise is | |
// resolved or rejected. And it should be fulfilled with the | |
// result of a callback, passed to .then. | |
// 4) What if a callback passed to .then returns a promise? | |
// No problem! We can check if it's a promise when we resolve | |
// our promise and wait for it to be done. See resolve function. | |
// 5) What if a callback to .then is a bad guy and throws us | |
// an error? We can catch it, reject out promise and thus | |
// let it go down the chain. | |
// 6) Implement .catch if you want. It's essentially a .then | |
// with the first callback equals to null. Don't forget to | |
// catch errors or they will be swallowed! By the way, have | |
// you noticed that the problem of synchronous try-catch is | |
// gone? :) | |
// Implementation | |
function Promise(fn){ | |
// Promise is a state machine actually | |
var value, handlers = [], state = 'pending'; | |
// Just executes our state machine listeners | |
function handle(handler) { | |
if (state==='fulfilled') | |
handler.onFulfill(value); | |
else if (state==='rejected') | |
handler.onReject(value); | |
else | |
handlers.push(handler); | |
} | |
// If promise is fulfilled with promise - | |
// we should resolve it too | |
function resolve(result){ | |
if (result instanceof Promise) | |
result.done(fulfill, reject); | |
else | |
fulfill(result); | |
} | |
// The functions for an actual resolving | |
// and changing state of state machine | |
function fulfill(result){ | |
state = 'fulfilled'; | |
value = result; | |
handlers.forEach(handle); | |
} | |
function reject(error){ | |
state = 'rejected'; | |
value = error; | |
handlers.forEach(handle); | |
} | |
// Nuffsaid | |
this.done = function(onFulfill, onReject){ | |
handle({ | |
onFulfill: onFulfill, | |
onReject: onReject, | |
}); | |
} | |
// .then is easier to implement via .done | |
this.then = function(onFulfill, onReject){ | |
var _this = this; | |
return new Promise(function(fulfill, reject){ | |
_this.done(function(value){ | |
// We need to check that onFulfill handler | |
// itself is not throwing any errors | |
try { | |
fulfill(onFulfill(value)); | |
} catch(e){ reject(e); } | |
}, function(error){ | |
if (onReject) | |
fulfill(onReject(error)); | |
else | |
reject(error); | |
}); | |
}); | |
} | |
// Convinience method, just a sugar | |
this.catch = function(onReject){ | |
var _this = this; | |
return new Promise(function(fulfill, reject){ | |
_this.done(null, function(error){ fulfill(onReject(error)); }); | |
}); | |
} | |
// Hurray, just start to execute our promise | |
fn(resolve, reject); | |
} | |
// Usage | |
new Promise(function(fulfill, reject){ | |
var promise = new Promise(function(fulfill, reject){ | |
setTimeout(function(){ | |
fulfill('200 ms passed! Not 300 ;)'); }, 200); | |
}); | |
setTimeout(function(){ fulfill(promise); }, 100); | |
}).then(function(value){ | |
console.log(value); | |
return 'I get it!'; | |
}).then(function(value){ | |
console.log(value); | |
return new Promise(function(fulfill, reject){ | |
fulfill("I'm fulfilled by default!"); }); | |
}).then(function(value){ | |
console.log(value); | |
throw new Error('Ke-ke-ke'); | |
}).catch(function(error){ console.log(error); }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment