db.set('key1', 'value1', function(err) {
if(err) throw err;
db.set('key2', 'value2', function(err) {
if(err) throw err;
db.set('key3', 'value3', function(err) {
if(err) throw err;
//...
});
});
});
And now imagine you need error recovery too
Maybe you called it a race condition or used a network of custom events?
someModel.on("fetch",function(){
app.trigger("needToRedrawThatOtherThing")
})
app.on("needToRedrawThatOtherThing",function(){
someOtherModel.reload()
someOtherModel.on("error",function(){
app.someModel.goBack()
});
})
Looks more familiar? This has more than one problem in it.
A promise is basically a function that is going to
tell you the result some time soon.
But the important stuff is chaining and error propagation.
.then
method.then()
doesIt returns a new promise that is fulfilled by the functions in arguments
var newPromise = initialPromise.then(function(value){
return 42 //value or another promise
}, function(){
//initialPromise rejected
})
newPromise.then(function(value){
//value === 42
})
.__. One promise with functions called on resolution
\_.
fetchSomething().then(fulfill, reject)
fetchSomething().then(function(theThing){
use(theThing)
}, function(error){
console.log('oh crap...')
})
.then(null, function(){})
==
.catch(function(){})
.fail(function(){}) //for old browsers
.__.__. A chain with no error handling
\ \
a().then(b).then(c)
This example is bad for your error handling
.__.__. A chain with error handling, but not for c()
\__\_.
a().then(b).then(c,errorHandling)
This example is bad for your error handling
.__.__. A chain with all errors handled
\__\__\_.
a().then(b).then(c).catch(errorHandling)
fetchSomething().then(function(theThing){
return use(theThing)
}).then(function(){
throw new Error("trololo")
}).catch(function(error){
console.log('oh crap...')
})
Always return the promise to some code that has error handling or finish it
function promiseStuff(){
return fetchSomething().then(function(theThing){
return use(theThing)
})
}
a().then(promiseStuff).catch(...)
or
fetchSomething().then(function(theThing){
return use(theThing)
}).done() //ends a chain and throws errors if not yet handled
some promise implementations will throw unhandled errors, but that's not in the standard
.__.__. A chain with error recovery
\_._/
a().then(b, errorRecovery).then(recoveredFromError)
fetchSomething().then(function(theThing){
return parse(theThing)
},function(error){
return backupValue
}).then(function(value){
use(value)
})
.__(if)__. A chain with a conditional invocation
a().then(conditionally).then(b)
fetchSomething().then(function(theThing){
if(!theThing){
return fetchSomethingElse() //also a promise
}
return theThing //just a value
}).then(function(value){
use(value)
})
fetchSomething().then(function(ignoredValue){
return fetchSomethingElse()
}).then(function(value){
use(value)
}).catch(function(err){
console.error(err);
})
fetchSomething().then(function(theThing){
return fetchSomethingElse().then(function(anotherThing){
if(theThing === anotherThing){
return theThing
}else{
throw new Error("No match")
}
})
}).then(function(value){
use(value)
}).catch(function(err){
console.error(err);
})
bad
fetchSomething = function(){
var url = createPath() //what if it throws?
return promiseFetch(url) //a promise for http req
}
good
fetchSomething = function(){
// just to start with
return Promise.resolve("whatever").then(function(){
//now an error thrown here will propagate
return createPath()
}).then(function(url){
return promiseFetch(url)
})
}
Do not use `.defer()` available in some implementations
fetchSomething = new Promise(function(resolve, reject){
resolve("it's ok")
//in case of failure
reject(Error("it went bad"))
})
var guessWho = "";
Promise.resolve("resolvedPromise").then(function(){
guessWho = "in"
})
guessWho = "out"
The final value is "in" because then callback is always asynchronous, even if the promise is already resolved when .then is called. No stackoverflows if you make an infinite loop
Let's add two numbers that you have to fetch separately in unknown order
function add(getX,getY,cb) {
var x, y;
getX( function(xVal){
x = xVal;
// both are ready?
if (y != undefined) {
cb( x + y ); // send along sum
}
} );
getY( function(yVal){
y = yVal;
// both are ready?
if (x != undefined) {
cb( x + y ); // send along sum
}
} );
}
Isn't that painful
Promise.all([fetchX(),fetchY()]).then(function(results){
return results[0]+results[1]
})
or
Promise.all([fetchX(),fetchY()]).spread(function(x,y){
return x+y
})
Feel free to stop understanding at any point
var queue = Promise.resolve(initialValue)
for(var i=0; i<todos.length; i++){
queue = queue.then(todos[i])
}
Still a little ugly, let's try again
var queue = todos.reduce(function(qu,todo){
return qu.then(todo)
},Promise.resolve(initialValue))
There's a oneliner for showoffs in Q's docs
fetchSomeList().catch(function(err){
if (err.statusCode==404){
return []
} else {
//rethrow all other errors
throw err
}
}).then(function(list){
return use(list)
}).catch(...)
If you need to use a standard node-style asymchronous function, no need to manually wrap it with promise constructors. All popular promise implementations have helpers for that
var readFile = Bluebird.promisify(require("fs").readFile);
And there's a converter, for a cleaner look
var readFile = Q.denodeify(require("fs").readFile);
Reminder: a promise cannot be resolved multiple times. If someone wants it to, please call them an idiot.
Worth mentioning - every retry is a new promise for an identical action. But if one failed, it cannot be repurposed.
Promise.resolve()
.catch()
at the end of a chain
Q.when($.get(...)) or Promise.resolve($.get(...))
@naugtur
http://naugtur.pl