- You can attach more than one callback to a single promise
- values and states (errors) get passed along
Converting callback functions to return promises
// take a callback function and change it to return a promise
Aplus.toPromise = function(fn) {
return function() {
// promise to return
var promise = Aplus();
//on error we want to reject the promise
var errorFn = function(data) {
promise.reject(data);
};
// fulfill on success
var successFn = function(data) {
promise.fulfill(data);
};
// run original function with the error and success functions
// that will set the promise state when done
fn.apply(this,
[errorFn, successFn].concat([].slice.call(arguments, 0)));
return promise;
};
};
Sequential Calls
var asyncAddOne = Aplus.toPromise(function(err, cb, val) {
setTimeout(function() {
cb(val + 1);
});
});
var asyncMultiplyTwo = APlus.toPromise(function(err, cb, val) {
setTimeout(function() {
cb(val * 2);
});
});
var asyncInverse = APlus.toPromise(function(err, cb, val) {
setTimeout(function() {
if (val === 0) {
return err('value is zero');
}
cb(1 / val);
});
});
var alertResult = function(value) {
alert(value);
};
asyncAddOne(1)
.then(asyncMultiplyTwo)
.then(asyncInverse)
.then(alertResult);
You can see that I wrote the asynchronous methods using normal callback style and converted them to return promises. You could Instead write them directly to use a promise like this:
var asyncAddOne = function(val) {
var promise = APlus();
setTimeout(function() {
promise.fulfill(val + 1);
});
return promise;
};
Error Handling
var asyncInverse = function(val) {
var promise = APlus();
if (val === 0) {
promise.reject('can not inverse zero');
}
setTimeout(function() {
APlus.fulfill(1 / val);
});
return promise;
};
Though we also have the option of just throwing an error instead which will pass along the value as the error thrown. To add a single error handling function we just need to tack it on to the end of our call:
var onError = function(err) {
console.error(err);
};
asyncAddOne(1)
.then(asyncMultiplyTwo)
.then(asyncInverse)
.then(alertResult)
.then(undefined, onError);
Note how the first argument is undefined as the second argument is used for the error. We could just have easily put the onError with the alertResult call, but then we wouldn’t catch any error from the alertResult function.
Pool
// resolve all given promises to a single promise
Aplus.pool = function() {
// get promises
var promises = [].slice.call(arguments, 0);
var state = 1;
var values = new Array(promises.length);
var toGo = promises.length;
// promise to return
var promise = Aplus();
// whenever a promise completes
var checkFinished = function() {
// check if all the promises have returned
if (toGo) {
return;
}
// set the state with all values if all are complete
promise.changeState(state, values);
};
// whenever a promise finishes check to see if they're all finished
for (var i = 0; i < promises.length; i++) {
(function(index) {
promises[index].then(function(value) {
// on success
values[index] = value;
toGo--;
checkFinished();
}, function(value) {
// on error
values[index] = value;
toGo--;
// set error state
state = 2;
checkFinished();
});
})(i);
};
// promise at the end
return promise;
};
Aplus.pool(
getName(),
getAddress()
).then(function(value) {
var name = value[0];
var address = value[1];
alert(name + ' lives at ' + address);
}, function() {
alert('unable to retrieve details');
});
In the above getName and getAddress both return promises – though it is possible to tweak the pool function logic so that any non-promise return value is just passed through directly. There are some implementations that do this such as jQuery’s $.when
Some fun
var racer = function(err, success, value) {
setTimeout(function() {
if (Math.random() < 0.05) {
err(value);
} else {
success(value);
}
}, Math.random() * 2000);
};
var promiseRacer = Aplus.toPromise(racer);
We’ve given it a 5% chance of error (or if you’re like me and pretending it’s a horse race – breaking it’s leg and not finishing). Now we need to write a function that will run them all:
// return the value of the first succesful promise
Aplus.first = function() {
// get all promises
var promises = [].slice.call(arguments, 0);
// promise to return
var promise = Aplus();
// if all promises error out then we want to return an error
Aplus.pool.apply(Aplus, promises).then(undefined, function(value) {
promise.reject(value);
});
// when there is a success we want to fulfill the promise
var success = function(value) {
promise.fulfill(value);
};
// listen for success on all promises
for (var i = 0; i < promises.length; i++) {
promises[i].then(success);
}
return promise;
};
I simple have to hook up the “then” success function to a single promise, as it can only be fulfilled once. You’ll also see that I’m using Aplus.pool as well. This is for error handling, we need to handle the case when all the “horses” break their leg. Now let’s race them!
Aplus.first(
promiseRacer('Binky'),
promiseRacer('Phar Lap'),
promiseRacer('Sea Biscuit'),
promiseRacer('Octagonal'),
promiseRacer('My Little Pony'),
).then(function(value) {
alert(value + ' wins!');
}, function() {
alert('no function finished');
});
Powered by WPeMatico