A Domain Specific Promise is a concept that I use to describe an object that has the same semantics as a Promise but is tailored to a specific domain. This means it has its own callback functions and a different set of resolutions that just fulfilled and rejected.
This concept has three main components:
- Constuctor/Resolver - A custom initializer function with hooks for each new resolution state
- Factory method - A utility factory method that is used to construct a new object based on just the payload provided. It will unwrap / proxy / prepare raw data and return a ready to chain Domain Specific Promise object
- Chainable API - Chainable callbacks for each resolution state which return new Domain Specific Promise objects so that the previous chain is not mutated
Constructor/Resolver
Like a Promise the constructor of the object takes an initializer function. This callback is executed immediately and is passed an object with hooks for each resolution state.
Each hook is a function that can take a value and has no ties to any this
contexts so they can be passed around the app or attached to event handlers all without loosing any context.
Example
class Example {
constructor(initFn) {
this.promise = new Promise((resolve, reject) => {
initFn({
error: reject,
resolveReason1: value => resolve({ reason: 'reason1', value }),
resolveReason2: value => resolve({ reason: 'reason2', value }),
resolveReason3: value => resolve({ reason: 'reason3', value })
});
});
}
}
Factory method
The factory method is a static method that can take any of the following and know how to return a new instance of the Domain Specific Object. The idea is to unwrap the values construct new promise chain and return a new and well constructed DomainSpecificPromise.
Example
static resolve(result) {
if (result instanceof DomainSpecificPromise) { return result; }
let newDomainSpecificPromise = Object.create(DomainSpecificPromise.prototype);
newDomainSpecificPromise.promise = Promise.resolve(result);
return newDomainSpecificPromise;
}
Chainable API
Each resolution will have a callback. They will be a no-op if the resolution is not applicable and allow the chaining to continue. If it is applicable then the return value from the function will result in a new instance of a DomainSpecificPromise and llow continued chaining. It should be able to accept a scalar value, promise, or a new DomainSpecificPromise (to mutate the resolution if desired).
Example
onReason1(fn) {
return DomainSpecificPromise.resolve(this.promise.then(result => {
if (result.reason !== 'reason1') { return result; }
let newValue = fn(result.value);
if (newValue instanceof DomainSpecificPromise) { return newValue.promise; }
return Promise.resolve(newValue).then(value => ({ reason: 'reason1', value }));
}));
}