Skip to content

Instantly share code, notes, and snippets.

@remydagostino
Created November 17, 2017 16:58
Show Gist options
  • Save remydagostino/09bbc3e493b3a4301247fdc1d011ebaa to your computer and use it in GitHub Desktop.
Save remydagostino/09bbc3e493b3a4301247fdc1d011ebaa to your computer and use it in GitHub Desktop.
Learning exercise for promises (advanced)
// 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