Created
November 17, 2017 16:58
-
-
Save remydagostino/09bbc3e493b3a4301247fdc1d011ebaa to your computer and use it in GitHub Desktop.
Learning exercise for promises (advanced)
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
// Here we try to use first principles to hack together a very scrappy version | |
// of a javascript Promise which doesn't implement the A+ spec. | |
// This is a learning exercise and there are many edge-cases for which this code | |
// does not handle (such as attaching several callbacks to one promise). | |
// A good challenge for your understanding might be to modify the code so that | |
// it could allow several callbacks to be attached to a single promise. | |
// An broken example is shown at the bottom of the file. | |
// If you're feeling very adventurous, then try to make it fulfill the A+ spec. | |
// Or don't. That would be a very hard exercise. | |
// ----------------------------------------- | |
// Synchronous version | |
function addSyc(a, b) { | |
return { | |
then: function(cb) { | |
return cb(a + b); | |
} | |
}; | |
} | |
addSyc(1, 2) | |
.then(function(result) { | |
return addSyc(result, 10); | |
}) | |
.then(function(result) { | |
return addSyc(result, 100); | |
}) | |
.then(function(result) { | |
console.log("answer", result); | |
}) | |
// Study the synchronous version first. | |
// Make sure you understand it. | |
// Test your understanding by modifying it. | |
// ... | |
// Are you ready...? | |
// Asynchronous version | |
// This is the version with the node-style callback | |
function addWithCallback(a, b, callback) { | |
setTimeout(function() { | |
callback(a + b); | |
}, 1000); | |
} | |
// This is the function which will make our "promise" | |
// Don't look at this yet - come back after looking at the next function | |
function futureValue(worker) { | |
// There is just one input here, the worker is a function which accepts a | |
// function as an argument. The worker will call the provided function when | |
// it has finished it's work. | |
// These variables are set later. | |
// "value" is set by the "worker" when it calls the callback function | |
// "callback" is set when "then" is called | |
var callback = null; | |
var value = null; | |
// This is important! | |
// We call the worker function straight away. | |
// We provide it with a function for it to call when it has finished its work. | |
worker(function(v) { | |
// When this code is running, the worker is done. | |
// ... store the result of calling the worker | |
value = v; | |
// ... if ".then" has already been called, then we should call the provided | |
// callback function now now because we are done with our work. | |
if (callback !== null) { | |
callback(value); | |
} | |
}); | |
// We must return an object ... | |
return { | |
// ... with a property called "then" which is a function, and when it is | |
// called, it should return another object with a property called "then" | |
// which is a function, and when that is called, it should return an object | |
// with a property called "then" which is a function, and when... | |
// | |
// Oh dear – things got a bit recursive. | |
// | |
// The "then" function takes a function as its argument, and that function | |
// will be expected to be called with our value (in the future) and will | |
// (sometimes) return an object with a function called ".then"... | |
then: function(nextFn) { | |
// Oh oh! Here comes the recursion! | |
return futureValue(function(done) { | |
// Hmm... where are we? | |
// | |
// It looks like I'm inside another callback. The way out is to | |
// call the "done" function. | |
// | |
// I can't call it yet though, I need to make sure the worker is | |
// finished its work. I'll set up another callback function to run | |
// when it is finished. | |
callback = function() { | |
// Yay! At this point our worker is done. | |
// We should run the function that was provided to "then" | |
var nextResult = nextFn(value); | |
// The "nextFn" could have provided us with: | |
// - Another future value | |
// - A synchronous value | |
// - No value at all. | |
// | |
// Let's check each option. | |
if (typeof nextResult !== 'undefined') { | |
if (typeof nextResult.then === 'function') { | |
// If we got another future value then we should attach a callback | |
// and only say that we are done when that one finishes. | |
// | |
// This is how we are able to achieve "nesting" without actually | |
// having our code be nested. | |
nextResult.then(function(nextResultValue) { | |
done(nextResultValue); | |
}); | |
} else { | |
// If we got a synchronous value then we're done. We can finish | |
// with that value. | |
done(nextResult); | |
} | |
} else { | |
// Otherwise we can say that we are done, and just to be nice | |
// lets return the value from one level up. | |
done(value); | |
} | |
}; | |
// It's possible that our worker has already finished. If that is the | |
// case then our callback may never have fired. | |
if (value !== null) { | |
callback(); | |
} | |
}); | |
} | |
}; | |
} | |
// This is the asynchronous version of add. It uses the "futureValue" | |
// and the node-style callback function "addWithCallback" | |
function add(a, b) { | |
return futureValue(function(done) { | |
addWithCallback(a, b, done); | |
}); | |
} | |
// Here we use the asynchronous add function | |
add(1, 2) | |
.then(function(result) { | |
return add(result, 10); | |
}) | |
.then(function(result) { | |
return add(result, 100); | |
}) | |
.then(function(result) { | |
return add(result, 1000); | |
}) | |
.then(function(result) { | |
console.log('addition:', result); | |
}); | |
// And here is an example of the "futureValue" function being used. | |
futureValue(function(done) { return done('hi'); }) | |
.then(function(answer) { | |
console.log('got answer:', answer); | |
}); | |
// Exercise: | |
// Modify the above code so that it is possible to attach two callbacks to one | |
// function. Currently, only one of these callbacks will fire – but which one? | |
var myPromise = add(5, 4); | |
myPromise.then(function(answer) { | |
console.log('First callback firing!', answer); | |
}); | |
myPromise.then(function(answer) { | |
console.log('Second callback firing!', answer); | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment