I have a trading algorithm placing orders depending on prices it receives from a stream of data.
When in production, we receive prices from a WS socket, so it is naturally asynchronous.
Though, we wanted to test our algorithm with pre-loaded prices sent to the algorithm synchronisly. The problem is that, as the algorithm is fully asynchronous in production, all function are marked async and have await calls in them. Though, in the "test" mode, all that async functions actually return Promise.resolve(whatever) immediately because everything is fake (prices are fake, placing order is fake etc.) and we don't wait for anything from the Internet.
The problem is that, as all these functions are asynchronous, despite they return immediately a promise.resolve(whatever), they are much slower that if they returned whatever directly.
My question is : is it possible to make "asyncOrNot" functions in JS/TS that would be called with "awaitOrNot" ? If not, what approach can be thought of in this situation to get rid of all the async overhead time, to make the "fake" environment faster ? I'd like to avoid to maintain 2 different algorithms (one production async one and another sync one for test purposes)
Just a little piece of code to understand what I'd like :
abstract class PriceStream {
onPriceCallback: (price: number) => PromiseOrNot<void>
abstract startStreaming(): void
}
class RealAsyncPriceStream extends PriceStream {
startStreaming() {
// this is not real WebSocket functions but it's just for you to understand
// that prices are arriving and sent asynchronously to the callback here
webSocket.onMessage((message) => {
const price = ... // do formatting of message to a price
this.onPriceCallback(price)
})
webSocket.start()
}
}
class FakeSyncPriceStream extends PriceStream {
startStreaming() {
const prices = [0.6, 0.8, 0.9, 0.10]
for (price of prices) {
this.onPriceCallback(price)
}
}
}
class Algo {
constructor(priceSteam: PriceStream) {
priceStream.onPriceCallback = this.onPrice.bind(this)
}
run() {
priceStream.startStreaming()
}
asyncOrNot onPrice(): PromiseOrNot<void> {
// perform business operations that are :
// - all asynchronous in production (placing and cancelling order awaits for the broker response)
// - all synchronous in fake environment : placing and cancelling orders only locally in memory : all async function return immediately Promise.resolve(...)
}
}
It's an interesting idea. Like I said in comment, it's possible with a custom promise impl that shadows the native impl. You need to apply it as polyfill to override globalThis.Promise.
With TS compile target set to ES2016 and lesser, compiled JS code will not using async/await language feature. Instead it uses generator and Promise, which will put your custom impl to work.
Below is my impl. It automatically adapts to sync and async behavior, depending on whether you synchronously call the resolve/reject callback when instantiating Promise. Not production ready because interface is not aligned to spec. But enough to demonstrate the idea.
TS Playground
const RealPromise = globalThis.Promise
class SyncPromise {
static resolve(value) {
return new SyncPromise((resolve) => resolve(value))
}
constructor(callback) {
this.status = 'pending'
this.resolve = (value) => {
this.status = 'resolved'
this.value = value
this.run()
}
this.reject = (value) => {
this.status = 'rejected'
this.value = value
this.run()
}
this.callbacks = []
try {
callback(this.resolve, this.reject)
} finally {
if (this.status === 'pending') {
return new RealPromise((resolve, reject) => {
this.callbacks.push([resolve, reject])
})
}
}
}
then(onFulfilled = (x) => x, onRejected = (x) => x) {
return new SyncPromise((rs, rj) => {
switch (this.status) {
case 'pending':
break
case 'resolved':
rs(onFulfilled(this.value))
break
case 'rejected':
rj(onRejected(this.value))
break
}
})
}
run() {
const callbacks = this.callbacks
this.callbacks = []
for (let [onFulfilled, onRejected] of callbacks) {
if (this.status === 'resolved') {
onFulfilled(this.value)
} else if (this.status === 'rejected') {
onRejected(this.value)
}
}
}
}
globalThis.Promise = SyncPromise
async function main() {
let value = await Promise.resolve(42)
console.log(value)
}
Related
Currently I have many concurrent identical calls to my backend, differing only on an ID field:
getData(1).then(...) // Each from a React component in a UI framework, so difficult to aggregate here
getData(2).then(...)
getData(3).then(...)
// creates n HTTP requests... inefficient
function getData(id: number): Promise<Data> {
return backend.getData(id);
}
This is wasteful as I make more calls. I'd like to keep my getData() calls, but then aggregate them into a single getDatas() call to my backend, then return all the results to the callers. I have more control over my backend than the UI framework, so I can easily add a getDatas() call on it. The question is how to "mux" the JS calls into one backend call, the "demux" the result into the caller's promises.
const cache = Map<number, Promise<Data>>()
let requestedIds = []
let timeout = null;
// creates just 1 http request (per 100ms)... efficient!
function getData(id: number): Promise<Data> {
if (cache.has(id)) {
return cache;
}
requestedIds.push(id)
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas(requestedIds).then((datas: Data[]) => {
// TODO: somehow populate many different promises in cache??? but how?
requestedIds = []
timeout = null
}
}, 100)
}
return ???
}
In Java I would create a Map<int, CompletableFuture> and upon finishing my backend request, I would look up the CompletableFuture and call complete(data) on it. But I think in JS Promises can't be created without an explicit result being passed in.
Can I do this in JS with Promises?
A little unclear on what your end goal looks like. I imagine you could loop through your calls as needed; Perhaps something like:
for (let x in cache){
if (x.has(id))
return x;
}
//OR
for (let x=0; x<id.length;x++){
getData(id[x])
}
Might work. You may be able to add a timing method into the mix if needed.
Not sure what your backend consists of, but I do know GraphQL is a good system for making multiple calls.
It may be ultimately better to handle them all in one request, rather than multiple calls.
The cache can be a regular object mapping ids to promise resolution functions and the promise to which they belong.
// cache maps ids to { resolve, reject, promise, requested }
// resolve and reject belong to the promise, requested is a bool for bookkeeping
const cache = {};
You might need to fire only once, but here I suggest setInterval to regularly check the cache for unresolved requests:
// keep the return value, and stop polling with clearInterval()
// if you really only need one batch, change setInterval to setTimeout
function startGetBatch() {
return setInterval(getBatch, 100);
}
The business logic calls only getData() which just hands out (and caches) promises, like this:
function getData(id) {
if (cache[id]) return cache[id].promise;
cache[id] = {};
const promise = new Promise((resolve, reject) => {
Object.assign(cache[id], { resolve, reject });
});
cache[id].promise = promise;
cache[id].requested = false;
return cache[id].promise;
}
By saving the promise along with the resolver and rejecter, we're also implementing the cache, since the resolved promise will provide the thing it resolved to via its then() method.
getBatch() asks the server in a batch for the not-yet-requested getData() ids, and invokes the corresponding resolve/reject functions:
function getBatch() {
// for any
const ids = [];
Object.keys(cache).forEach(id => {
if (!cache[id].requested) {
cache[id].requested = true;
ids.push(id);
}
});
return backend.getDatas(ids).then(datas => {
Object.keys(datas).forEach(id => {
cache[id].resolve(datas[id]);
})
}).catch(error => {
Object.keys(datas).forEach(id => {
cache[id].reject(error);
delete cache[id]; // so we can retry
})
})
}
The caller side looks like this:
// start polling
const interval = startGetBatch();
// in the business logic
getData(5).then(result => console.log('the result of 5 is:', result));
getData(6).then(result => console.log('the result of 6 is:', result));
// sometime later...
getData(5).then(result => {
// if the promise for an id has resolved, then-ing it still works, resolving again to the -- now cached -- result
console.log('the result of 5 is:', result)
});
// later, whenever we're done
// (no need for this if you change setInterval to setTimeout)
clearInterval(interval);
I think I've found a solution:
interface PromiseContainer {
resolve;
reject;
}
const requests: Map<number, PromiseContainer<Data>> = new Map();
let timeout: number | null = null;
function getData(id: number) {
const promise = new Promise<Data>((resolve, reject) => requests.set(id, { resolve, reject }))
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas([...requests.keys()]).then(datas => {
for (let [id, data] of Object.entries(datas)) {
requests.get(Number(id)).resolve(data)
requests.delete(Number(id))
}
}).catch(e => {
Object.values(requests).map(promise => promise.reject(e))
})
timeout = null
}, 100)
}
return promise;
}
The key was figuring out I could extract the (resolve, reject) from a promise, store them, then retrieve and call them later.
I wonder if someone ever had this issue before.
Before I had an EventHandler that would look like this:
export interface EventHandler {
name: string;
canHandleEvent(event: EventEntity): boolean;
handleEvent(event: EventEntity): Promise<void>;
}
And my filter function would work normally, also my tests were passing - where I was filtering the events using:
messages.forEach(message => {
const event: EventEntity = JSON.parse(message.Body);
this.handlers
.filter(handler => handler.canHandleEvent(event)) // WORKED WELL
.forEach(handler => {
// LOGIC
});
Currently, we had to change the canHandleEvent to either Boolean or Promise. Since we had some promises to be resolved and identify whether the event can be handle or not.
export interface EventHandler {
// ...
canHandleEvent(event: EventEntity): boolean | Promise<boolean>;
}
So, in order to solve it, I used Promise.resolve and Promise.all. No luck:
messages.forEach(async message => {
const event: EventEntity = JSON.parse(message.Body);
const handlersResolved = await Promise.all(this.handlers);
handlersResolved
.filter(handler => handler.canHandleEvent(event))
.forEach(handler => {
Now, my tests pass for the Promise canHandleEvent, but they are failing for the events passed that is boolean. They look like this:
class HandlerB implements EventHandler {
name = HandlerB.name;
numRuns = 0;
canHandleEvent(event: EventEntity): boolean {
console.log('event', event)
return event.eventType === EventType.ONE_EVENT || event.eventType === EventType.SECOND_EVENT;
}
async handleEvent(event: EventEntity): Promise<void> {
return new Promise(resolve => {
setTimeout(() => {
this.numRuns += 1;
resolve();
}, 25);
});
}
}
And my test that are now failing and before was passing are:
it('Should process handlers that match, including canHandleEvent that returns Promise<boolean> TRUE', async () => {
setHandlers([handlerA, handlerB, handlerC]);
const event = await createEvent(EventType.SECOND_EVENT);
await sleep(1000);
expect(handlerA.numRuns, 'handleA').to.eql(0);
expect(handlerB.numRuns, 'handleB').to.eql(1);
expect(handlerC.numRuns, 'handleC').to.eql(1); // handlerC is Promise<boolean>, it works fine
expect(errorHandler.numRuns).to.eql(0);
handlerC.numRuns = 0;
});
it('Should allow handlers to pass even if one has an error', async () => {
setHandlers([handlerA, handlerB, errorHandler]);
const event = await createEvent(EventType.USER_REGISTRATION_STATUS);
await sleep(1000);
expect(handlerA.numRuns, 'handlerA').to.eql(1);
expect(handlerB.numRuns, 'handlerB').to.eql(1);
expect(errorHandler.numRuns, 'errorHandler').to.eql(1);
});
Any thoughts on how to solve this? I've tried to identify whether is promise or boolean before inside the .filter but still no luck:
this.handlers
.filter(async handler => {
if(typeof handler.canHandleEvent(event).then == 'function') {
const result = await Promise.resolve(handler.canHandleEvent(event))
console.log('IS PROMISE!!', result);
return result
}
console.log('IT IS NOT PROMISE', handler.canHandleEvent(event))
return handler.canHandleEvent(event)
})
Currently, we had to change the canHandleEvent to either Boolean or Promise...
Just to be clear, that's a massive semantic change that will ripple through every layer of the code that uses that method. You can't directly use filter with it anymore, for instance, and any synchronous function that uses it is now potentially asynchronous (and fundamentally, "potentially asynchronous" = "asynchronous"). But if it has to happen, it has to happen! :-)
Your original code using canHandleEvent like this:
messages.forEach(message => {
const event: EventEntity = JSON.parse(message.Body);
this.handlers
.filter(handler => handler.canHandleEvent(event)) // WORKED WELL
.forEach(handler => {
// LOGIC
});
});
has to become asynchronous, like this:
// Handles things in parallel, not series
await/*or return*/ Promise.all(messages.map(message => {
const event: EventEntity = JSON.parse(message.Body);
return Promise.all(this.handlers.map(handler => async {
if (await handler.canHandleEvent(event)) {
// LOGIC
}
}));
}));
Notice how each layer was affected. messages.forEach turned into building an array of promises via messages.map and waiting for them via await (or using .then, etc., or returning to a calling function). For each message, we do the same thing for handlers, since we can't know whether a handler can handle something synchronously. (No need for Promise.resolve, Promise.all will handle that for you.)
The code above assumes it's okay for all of this to overlap (both the messages and the handlers for a message), whereas before because it was all synchronous, they all happened in series (all of the relevant handlers for one message, in order, then all of the handlers for the next, etc.). If you need it to be in series like that, you can use for-of loops:
// Handles things in series, not parallel
// (In an `async` function)
for (const message of messages) {
const event: EventEntity = JSON.parse(message.Body);
for (const handler of this.handlers) {
if (await handler.canHandleEvent(event)) {
// LOGIC
}
}
}
In both cases, it's possible to handle the ones returning boolean differently (synchronously) from the ones returning promises, but it complicates the code.
To solve your issue, I think the most simple way is to first populate the array with the expected values, so you can properly filter.
const transformedHandlers = await Promise.all(this.handlers.map(async handler => {
return {
...handler,
eventCanBeHandled: await handler.canHandleEvent(event)
}
}))
This will transform the array so you have a key that shows what handlers can be handled.
To finish it off you use your code like you would always do but instead of checking
canHandleEvent
you use the new field that has been introduced in the const transformedhandlers
below is the example:
transformedHandlers
.filter(handler => handler.eventCanBeHandled)
.forEach(handler => {
// LOGIC
});
This should be enough to keep you code working like it used to.
sorry for my English. it's not my native language
I am looking at https://www.promisejs.org/patterns/ and it mentions it can be used if you need a value in the form of a promise like:
var value = 10;
var promiseForValue = Promise.resolve(value);
What would be the use of a value in promise form though since it would run synchronously anyway?
If I had:
var value = 10;
var promiseForValue = Promise.resolve(value);
promiseForValue.then(resp => {
myFunction(resp)
})
wouldn't just using value without it being a Promise achieve the same thing:
var value = 10;
myFunction(10);
Say if you write a function that sometimes fetches something from a server, but other times immediately returns, you will probably want that function to always return a promise:
function myThingy() {
if (someCondition) {
return fetch('https://foo');
} else {
return Promise.resolve(true);
}
}
It's also useful if you receive some value that may or may not be a promise. You can wrap it in other promise, and now you are sure it's a promise:
const myValue = someStrangeFunction();
// Guarantee that myValue is a promise
Promise.resolve(myValue).then( ... );
In your examples, yes, there's no point in calling Promise.resolve(value). The use case is when you do want to wrap your already existing value in a Promise, for example to maintain the same API from a function. Let's say I have a function that conditionally does something that would return a promise — the caller of that function shouldn't be the one figuring out what the function returned, the function itself should just make that uniform. For example:
const conditionallyDoAsyncWork = (something) => {
if (something == somethingElse) {
return Promise.resolve(false)
}
return fetch(`/foo/${something}`)
.then((res) => res.json())
}
Then users of this function don't need to check if what they got back was a Promise or not:
const doSomethingWithData = () => {
conditionallyDoAsyncWork(someValue)
.then((result) => result && processData(result))
}
As a side node, using async/await syntax both hides that and makes it a bit easier to read, because any value you return from an async function is automatically wrapped in a Promise:
const conditionallyDoAsyncWork = async (something) => {
if (something == somethingElse) {
return false
}
const res = await fetch(`/foo/${something}`)
return res.json()
}
const doSomethingWithData = async () => {
const result = await conditionallyDoAsyncWork(someValue)
if (result) processData(result)
}
Another use case: dead simple async queue using Promise.resolve() as starting point.
let current = Promise.resolve();
function enqueue(fn) {
current = current.then(fn);
}
enqueue(async () => { console.log("async task") });
Edit, in response to OP's question.
Explanation
Let me break it down for you step by step.
enqueue(task) add the task function as a callback to promise.then, and replace the original current promise reference with the newly returned thenPromise.
current = Promise.resolve()
thenPromise = current.then(task)
current = thenPromise
As per promise spec, if task function in turn returns yet another promise, let's call it task() -> taskPromise, well then the thenPromise will only resolve when taskPromise resolves. thenPromise is practically equivalent to taskPromise, it's just a wrapper. Let's rewrite above code into:
current = Promise.resolve()
taskPromise = current.then(task)
current = taskPromise
So if you go like:
enqueue(task_1)
enqueue(task_2)
enqueue(task_3)
it expands into
current = Promise.resolve()
task_1_promise = current.then(task_1)
task_2_promise = task_1_promise.then(task_2)
task_3_promise = task_2_promise.then(task_3)
current = task_3_promise
effectively forms a linked-list-like struct of promises that'll execute task callbacks in sequential order.
Usage
Let's study a concrete scenario. Imaging you need to handle websocket messages in sequential order.
Let's say you need to do some heavy computation upon receiving messages, so you decide to send it off to a worker thread pool. Then you write the processed result to another message queue (MQ).
But here's the requirement, that MQ is expecting the writing order of messages to match with the order they come in from the websocket stream. What do you do?
Suppose you cannot pause the websocket stream, you can only handle them locally ASAP.
Take One:
websocket.on('message', (msg) => {
sendToWorkerThreadPool(msg).then(result => {
writeToMessageQueue(result)
})
})
This may violate the requirement, cus sendToWorkerThreadPool may not return the result in the original order since it's a pool, some threads may return faster if the workload is light.
Take Two:
websocket.on('message', (msg) => {
const task = () => sendToWorkerThreadPool(msg).then(result => {
writeToMessageQueue(result)
})
enqueue(task)
})
This time we enqueue (defer) the whole process, thus we can ensure the task execution order stays sequential. But there's a drawback, we lost the benefit of using a thread pool, cus each sendToWorkerThreadPool will only fire after last one complete. This model is equivalent to using a single worker thread.
Take Three:
websocket.on('message', (msg) => {
const promise = sendToWorkerThreadPool(msg)
const task = () => promise.then(result => {
writeToMessageQueue(result)
})
enqueue(task)
})
Improvement over take two is, we call sendToWorkerThreadPool ASAP, without deferring, but we still enqueue/defer the writeToMessageQueue part. This way we can make full use of thread pool for computation, but still ensure the sequential writing order to MQ.
I rest my case.
I've found somewhere implementation of the mutex pattern in JavaScript that works well but it looks really complex. Even, though I understand mutex pattern I don't know why promises in some places are being used in a given way. But about that in a moment. I was playing around with it and I've created my own implementation that is much easier to understand and has less code. Here is project: https://codesandbox.io/s/mutex-2e014
You have there 3 implementations of the mutex pattern. Mutex 0 and 1 is the same version with this difference, that in the version 1, I've simplified code by taking advantage of arrow functions and being able to access this from the parent context. So overall they are the same solutions. My solution is Mutex 2.
You can test code by opening console and clicking buttons from 0 to 2. You can see that each asynchronous action takes 1 second and it will always execute operations in proper order and wait for the previous task to finish. It works the same way in all versions. Also when you hold the SHIFT key, when pressing buttons it will throw an exception and that part is also working the same in all versions. Next thing, I had to compare is return value and that's not different. So my questions is why is it so complex in the first 2 solutions? Am I missing something? Does it really work the same way?
Now the question about the promise usage that I don't understand. In the Mutex1.ts file in line 7, we have return new Promise(resolve);. We’re passing resolve from the parent promise as the first argument of the Promise constructor which is executor. And from what I read executors are being invoked just before promise creation. In this case, thanks to that we have access to the unlock function in the run method but I don’t know how it’s possible that this.lock returns function. Probably, I don't know promises well enough. Any ideas?
Mutex 0
export default class MutexA {
private mutex = Promise.resolve();
lock() {
let begin = unlock => {};
this.mutex = this.mutex.then(() => {
return new Promise(begin);
});
return new Promise(res => {
begin = res;
});
}
async run(fn) {
const unlock = await this.lock();
try {
return await Promise.resolve(fn());
} finally {
unlock();
}
}
}
Mutex 1
export default class MutexB {
private mutex = Promise.resolve();
lock() {
return new Promise(resolve => {
this.mutex = this.mutex.then(() => {
return new Promise(resolve);
});
});
}
async run(fn) {
const unlock = await this.lock();
try {
return await fn();
} finally {
unlock();
}
}
}
Mutex 2
export default class MutexC {
private mutex = Promise.resolve();
async run(fn) {
return new Promise((resolve, reject) => {
this.mutex = this.mutex.then(async () => {
try {
resolve(await fn());
} catch (err) {
reject(err);
}
});
});
}
}
I have implemented a blackboard pattern in Javascript, my blackboard control iterates over knowledge sources / experts, and call their execAction().
for(let expert of this.blackboard.experts){
// Check execution condition
}
mostRelevantExpert.executeAction();
Now the problem is, those knowledge sources often need to call remote APIs or read files, and most of the libraries only provide callback APIs
class myExpert{
executeAction() {
myLibrary.call(params, (error, response) => { continueHere; })
}
}
Of course this is completely messing up the flow of my blackboard.
I am not sure whether the solution would be to reimplement the whole blackboard in an "asynchronous" fashion, or if there's a smarter way to go.
I've tried using libraries like deasync, but the problem is that I actually have a bug in myLibrary.call(params, (error, response) => { bueHere; } and I do not really understand now how to debug it. Since I am likely to have more problems like that in the future, was wondering what actions I should take.
Using node 6, ES6, and I don't like using callback programming style for what I'm doing here.
How should I go about the blackboard pattern in Javascript ?
How can I debug async code using node debug app.js
EDIT :
Here is my Blackboard Control code :
module.exports = class BlackboardControl{
constructor(blackboard){
this.blackboard = blackboard;
}
loop(){
console.log('¤ Blackboard Control');
console.log(' Starting Blackboard loop');
// Problem solved when there is a technicianAnswer, so the bot has something to say
while(!this.blackboard.problemSolved) {
// Select experts who can contribute to the problem
let candidates = [];
for(let expert of this.experts){
let eagerness = expert.canContribute();
if(eagerness){
candidates.push([eagerness,expert]);
}
}
if(candidates.length === 0) {
console.log('No expert can\'t do anything, returning');
return;
}
// Sort them by eagerness
candidates.sort(function(a,b) {
return a[0]-b[0];
});
for(let eagerExpert of candidates){
console.log('Next expert elected : ' + eagerExpert[1].constructor.name);
eagerExpert[1].execAction();
}
}
}
};
I haven't actually tried it out, yet (largely because I'd have to invent arbitrary problem spaces, and I feel like it would be much easier traveling in the other direction, right now)...
But if you want a look at what an async flow might look like, I might consider something like this:
async function getEngagedExperts (experts, problem) {
const getContributor = expert => expert.canContribute(problem)
.then(eagerness => [eagerness, expert]);
const contributors = await Promise.all(experts.map(getContributor));
return contributors.filter(([eager]) => eager);
}
async function contribute (previousState, expert) {
const state = await previousState;
return expert.execAction(state);
}
async function solveProblem (problem, experts) {
if (problem.solved) { return problem; }
const candidates = (await getEngagedExperts(experts, problem))
.sort(([a], [b]) => a - b)
.map(([, expert]) => expert);
const result = await candidates.reduce(contribute, Promise.resolve(problem));
return candidates.length ? solveProblem(result, experts) : undefined;
}
ES6 Generators + Yield
Works in ES6, if you have a library like co to manage the promises returned from iterators.
Writing your own co implementation is not that difficult, but this is totally not the space for it.
const getEngagedExperts = co.wrap(function * getEngagedExperts (experts, problem) {
const getContributor = expert => expert.canContribute(problem)
.then(eagerness => [eagerness, expert]);
const contributors = yield Promise.all(experts.map(getContributor));
return contributors.filter(([eager]) => eager);
});
const contribute = co.wrap(function * contribute (previousState, expert) {
const state = yield previousState;
return expert.execAction(state);
});
const solveProblem = co.wrap(function * solveProblem (problem, experts) {
if (problem.solved) { return problem; }
const candidates = (yield getEngagedExperts(experts, problem)))
.sort(([a], [b]) => a - b)
.map(([, expert]) => expert);
const result = yield candidates.reduce(contribute, Promise.resolve(problem));
return candidates.length ? solveProblem(result, experts) : undefined;
});
ES5 + Promise
When all else fails, write it by hand, in good ol' ES5, plus promises.
function getEngagedExperts (experts, problem) {
function getContributor (expert) {
return expert.canContribute(problem).then(eagerness => [eagerness, expert]);
}
function filterContributors (contributors) {
return contributors.filter(function (pair) {
const eagerness = pair[0];
return eagerness;
});
}
const getContributors = Promise.all(experts.map(getContributor));
return getContributors.then(filterContributors);
}
function contribute (previousComputation, expert) {
return previousComputation.then(function (state) {
return expert.execAction(state);
});
}
function solveProblem (problem, experts) {
if (problem.solved) { return problem; }
const getCandidates = getEngagedExperts(experts, problem)
.then(function (candidates) {
return candidates
.sort(function (a, b) { return a[0] - b[0]; })
.map(function (pair) { return pair[1]; });
});
return getCandidates.then(function (candidates) {
const getResult = candidates.reduce(contribute, Promise.resolve(problem));
return getResult.then(function (result) {
return candidates.length ? solveProblem(result, experts) : undefined;
});
});
}
Here is an attempt based on my (incomplete) understanding of your problem. These are the premises I used:
you have Expert objects that provide an asynchronous function that does some kind of work via the executeAction() method.
you have a BlackboardControl object that pools these experts and is responsible for running them in sequence until one of them returns a successful result. This object is also holding some kind of state encapsulated in the blackboard property.
The first step to a promise-based solution is to make the executeAction() method return a promise instead of requiring a callback. Changing the call convention of an entire node-style library is easily done with the promisifyAll() utility that Bluebird provides:
// module MyExpert ---------------------------------------------------------
var Promise = require('bluebird');
// dummy library with a node-style async function, let's promisify it
var myLibrary = Promise.promisifyAll({
someFunc: function (params, callback) {
setTimeout(() => {
if (Math.random() < 0.4) callback('someFunc failed');
else callback(null, {inputParams: params});
}, Math.random() * 1000 + 100);
}
});
class MyExpert {
executeAction(params) {
return myLibrary.someFuncAsync(params); // returns a promise!
}
}
module.exports = MyExpert;
now, we need a BlackboardControl object that does two things: pull out the next free Expert object from the pool (nextAvailableExpert()) and solve a given problem by applying experts to it in sequence, until one of them succeeds or a maximum retry count is reached (solve()).
// module BlackboardControl ------------------------------------------------
var Promise = require('bluebird');
var MyExpert = require('./MyExpert');
class BlackboardControl {
constructor(blackboard) {
this.blackboard = blackboard;
this.experts = [/* an array of experts */];
}
nextAvailableExpert() {
return new MyExpert();
// yours would look more like this
return this.experts
.map((x) => ({eagerness: x.canContribute(), expert: x}))
.filter((ex) => ex.eagerness > 0)
.sort((exA, exB) => exA.eagerness - exB.eagerness)
.map((ex) => ex.expert)
.pop();
}
solve(options) {
var self = this;
var expert = this.nextAvailableExpert();
if (!expert) {
return Promise.reject('no expert available');
} else {
console.info('Next expert elected : ' + expert.constructor.name);
}
options = options || {};
options.attempt = +options.attempt || 0;
options.maxAttempts = +options.maxAttempts || 10;
return expert.executeAction(/* call parameters here */).catch(error => {
options.attempt++;
console.error("failed to solve in attempt " + options.attempt + ": " + error);
if (options.attempt <= options.maxAttempts) return self.solve(options);
return Promise.reject("gave up after " + options.maxAttempts + " attempts.");
});
}
}
module.exports = BlackboardControl;
The key line is this one:
if (options.attempt <= options.maxAttempts) return self.solve(options);
Promises chain. If you return a new promise from a promise callback (in this case from the catch() handler, since we want to start over when an expert fails) the overall result of the promise will be determined by the result of this new promise. In other words, the new promise will be executed. This is our iterative step.
This way returning a promise from solve() enables internal repetition by simply calling solve() again in the error handler - and it enables reacting externally via then() as shown in below example usage:
// usage -------------------------------------------------------------------
var BlackboardControl = require('./BlackboardControl');
var bbControl = new BlackboardControl({ /* blackboard object */ });
var result = bbControl.solve({
maxAttempts: 10
}).then(response => {
console.log("continueHere: ", response);
}).catch(reason => {
console.error(reason);
});
which creates output like this (here dummy function happened to fail five times in a row):
Next expert elected : MyExpert
failed to solve in attempt 1: Error: someFunc failed
Next expert elected : MyExpert
failed to solve in attempt 2: Error: someFunc failed
Next expert elected : MyExpert
failed to solve in attempt 3: Error: someFunc failed
Next expert elected : MyExpert
failed to solve in attempt 4: Error: someFunc failed
Next expert elected : MyExpert
failed to solve in attempt 5: Error: someFunc failed
Next expert elected : MyExpert
continueHere: { some: 'parameters' }
During expert runs control is returned to the main program. Due to the fact that now multiple experts can run at the same time on multiple problems we can't make a list of available experts up-front. We must make a fresh decision every time we need an expert, hence the nextAvailableExpert() function.
Ah right, I actually managed to make the deasync code work. It turns out I was trying to use
const deasync = require('deasync');
try {
const deasyncAnswer = deasync(Lib.foo(
myParam,
// Callback was here
);
}
But the correct way to use it was
const fooDeasynced= deasync(Lib.foo);
try {
const deasyncAnswer = fooDeasynced(myparams)
}