JavaScript Chain of Responsibility pattern with Promises
Chain of Responsibility is a handy pattern for having multiple entities that could resolve a certain task. These entities get a chance to resolve the task one by one until one of them does that (or all fail).
Definition
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. There is a potentially variable number of "handler" objects and a stream of requests that must be handled. Need to efficiently process the requests without hard-wiring handler relationships and precedence, or request-to-handler mappings.
Let’s go with an example. Let’s say that your application displays youtube videos. Initially you have only the video ID. Your Chain of Responsibility is composed from:
- Application cache (a plain JS Object)
- Browser cache
- Your server cache (you want to save on bills)
- YouTube API (last resort)
One important thing to take into account is that some requests may be asynchronous (the last 2). So we have to use callbacks or Promises. Actually using Promises we’ll be able to handle rejections and errors.
function step1() {
return new Promise(function(resolve, reject) {
console.log('step1')
// setTimeout(resolve, 1000)
setTimeout(reject, 1000)
})
}
function step2() {
return new Promise(function(resolve, reject) {
console.log('step2')
// setTimeout(resolve, 1000)
setTimeout(reject, 1000)
})
}
function step3() {
return new Promise(function(resolve, reject) {
console.log('step3')
setTimeout(resolve, 1000) // We don't pass data into resolve to make this example simpler
// setTimeout(reject, 1000)
})
}
step1()
.catch(step2)
.catch(step3)
.then(function(){
console.log('done')
})
.catch(function(){
console.log('failed')
})
In this case you’ll get following output:
step1
step2
step3
done
Changing the step2
to succeed will output
step1
step2
done
And making all steps to fail will output
step1
step2
step3
fail
So the idea is that each next step is called only if all previous steps failed. If at least one point succeeds then function passed to then
is called, otherwise function passed to last catch
is called.
And in order to pass data from steps to function passed to then
you simply have to call resolve
function with your data:
function step2() {
return new Promise(function(resolve, reject) {
resolve({done: true})
})
}
Sources
- (article) Chain or Responsibility pattern