Node.js - promises and conidtional statements (if, switch, etc) - how to structure? - javascript

Can you recommend how to correctly deal with a control flow with many if/switch and promises? All the tutorials on the Internet that I've found tend to deal with simple control flow, without many (any?) different processing branches. Any suggested reading or at least search terms?
The way I do it now is to encapsulate if/switch logic in a function that returns a Promise after evaluating the conditions and returns to the main process loop. Any way to do it better, nicer?
Sample code:
// Check if argument is a valid URL
Promise.promisify(checkUrl)().then(() => {
// Delete all query parameters from URL if present
return sanitizer.cleanAsync(argv.url)
}).then(_cleanUrl => {
cleanUrl = _cleanUrl;
logger.warn(`URL: ${cleanUrl}`);
// Validate Google Analytics view id supplied as '--gaId=<id>' command line argument or exit if it is not present
return Promise.promisify(checkGaId)()
}).then(() => {
// Check if DB exists, if not create it
return db.checkIfDatabaseExistsAsync()
}).then(() => {
// Check if all tables exist, if not create them
return db.checkTablesAsync()
}).then(() => {
// Check DB integrity (possiblDelete all query parameters from URL if presente to turn off in the config)
if (config.database.checkIntegrity) {
return db.integrityChecksAsync();
}
}).then(() => {
// Check if URL already exists in DB, if not insert it
return db.getOrCreateEntryUrlIdAsync(cleanUrl)
}).then(_entryId => {
entryId = _entryId;
// Check if any previous executions for the entry point exist and if so whether the last one completed
return db.getLastExecutionDataAsync(entryId);
}).then(lastExecution => {
// If last execution was not completed prompt for user action
return processLastExecution(entryId, lastExecution)
}).then(_pages => {
... more code follows here...
And psuedo-code for processLasExecution function:
function processLastExecution(entryId, lastExecution) {
return new Promise(
function (resolve, reject) {
// No previous executions found or all was okay
if (lastExecution == null || (lastExecution != null && lastExecution.is_completed == 'Y')) {
...resolves with A;
} else {
Promise.promisify(selectRunOption)().then(option => {
switch (option) {
case 'resume':
...resolves with B;
break;
case 'ignore':
...resolves with C;
break;
case 'delete':
...resolves with D;
break;
default:
...rejects
}
});
}
}
)
}
Any way of having the if/switch logic better/more clearly encapsulated or served?
Oh, if anyone wonders this is a command line script, not a web application, and this not exactly what Node.js was intended for.

I think it is better to use generator, then you can write sync like codes:
co(function* () {
// Check if argument is a valid URL
if (yield checkUrl) {
var cleanUrl = yield sanitizer.cleanAsync(argv.url);
...
}
...
}, ...
co can cooperation with callback and promise, see https://github.com/tj/co

Related

While loop with 2 conditions not working as expected

I want my while loop to match 2 conditions in order to break the loop - res has to NOT be undefined, which is the case only when status code is 200, and obj.owner has to be equal to some value i set. It takes a couple of seconds for the owner on that page to update, so i need the while loop to run until the owner updates.
Here's the request function
const getOwner = () => {
const opts = {
url: 'https://example.com',
json: true,
simple: false,
resolveWithFullResponse: true
}
return request(opts)
.then(res => {
if (res.statusCode == 200) {
const obj = {
owner: res.body.owner
}
return obj
} else {
console.error(`Error: ${res.statusCode}`)
}
})
.catch(e => {
console.error(`Panic: ` + e.message)
})
}
And here's the while loop
let owner = await getOwner()
while (!owner && owner.owner != 'some value') {
owner = await getOwner()
}
console.log('Loop broken')
When I console.log(!owner, (owner.owner != 'some value')) after the first function call, before trying to enter into the loop, it returns false for the first condition, and true for the second.
When status code is other than 200, it doesnt return the object, so i need those 2 conditions together, as otherwise it crashes the program due to owner being undefined.
Now, when i execute this, it always skips the loop no matter what, im really lost at this point.
In my opinion, it's a bad idea to loop through a resource until you get the intended result you are seeking.
If you have control over the resource that you are calling, change it to return the result according to your expectation.
If you do not have control over the resource, in my opinion, the resource should not be return different results on different calls. If you get a different response on each call, then the resource is not idempotent. It's a bad design.
Imagine, you would be looping n times, with that you may or may not get the results you are looking for.
I would suggest looking into implementation and fixing the root of the problem, rather than work around.
getOwner returns a Promise. You will need to await for it. And I don't think you really need the while loop:
let owner = await getOwner();
if(owner && owner.owner != 'some value') {
/*do stuff with owner*/
}
console.log(owner);

Node.js for loop using previous values?

Currently, I am trying to get the md5 of every value in array. Essentially, I loop over every value and then hash it, as such.
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (var userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
console.log(userHashString.toUpperCase() + "this is the hashed string")
if (userHashString.toUpperCase() === poster){
return console.log("this is the poster")
}
else {
..
}
}
else {
return null
}
})
)}
However, this leads to two problems. The first is that I am receiving the error warning "Don't make functions within a loop". The second problem is that the hashes are all returning the same. Even though every userID is unique, the userHashString is printing out the same value for every user in the console log, as if it is just using the first userID, getting the hash for it, and then printing it out every time.
Update LATEST :
exports.sendNotificationForPost = functions.firestore
.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data()
const watching = value.watchedBy
const poster = value.poster
const postContentNotification = value.post
const refPromises = []
var crypto = require('crypto');
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
for (let userID in watching) {
refPromises.push(admin.database().ref('notifications/'+ userID).once('value', (snapshot) => {
if (snapshot.exists()) {
const userHashString = userHash(userID)
if (userHashString.toUpperCase() === poster){
return null
}
else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload)
}
}
else {
return null
}
})
)}
return Promise.all(refPromises);
});
You have a couple issues going on here. First, you have a non-blocking asynchronous operation inside a loop. You need to fully understand what that means. Your loop runs to completion starting a bunch of non-blocking, asynchronous operations. Then, when the loop finished, one by one your asynchronous operations finish. That is why your loop variable userID is sitting on the wrong value. It's on the terminal value when all your async callbacks get called.
You can see a discussion of the loop variable issue here with several options for addressing that:
Asynchronous Process inside a javascript for loop
Second, you also need a way to know when all your asynchronous operations are done. It's kind of like you sent off 20 carrier pigeons with no idea when they will all bring you back some message (in any random order), so you need a way to know when all of them have come back.
To know when all your async operations are done, there are a bunch of different approaches. The "modern design" and the future of the Javascript language would be to use promises to represent your asynchronous operations and to use Promise.all() to track them, keep the results in order, notify you when they are all done and propagate any error that might occur.
Here's a cleaned-up version of your code:
const crypto = require('crypto');
exports.sendNotificationForPost = functions.firestore.document('posts/{posts}').onCreate((snap, context) => {
const value = snap.data();
const watching = value.watchedBy;
const poster = value.poster;
const postContentNotification = value.post;
function userHash(userIDstring) {
return crypto.createHash('md5').update(userIDstring).digest('hex');
}
return Promise.all(Object.keys(watching).map(userID => {
return admin.database().ref('notifications/' + userID).once('value').then(snapshot => {
if (snapshot.exists()) {
const userHashString = userHash(userID);
if (userHashString.toUpperCase() === poster) {
// user is same as poster, don't send to them
return {response: null, user: userID, poster: true};
} else {
const payload = {
notification: {
title: "Someone posted something!",
body: postContentNotification,
sound: 'default'
}
};
return admin.messaging().sendToDevice(snapshot.val(), payload).then(response => {
return {response, user: userID};
}).catch(err => {
console.log("err in sendToDevice", err);
// if you want further processing to stop if there's a sendToDevice error, then
// uncomment the throw err line and remove the lines after it.
// Otherwise, the error is logged and returned, but then ignored
// so other processing continues
// throw err
// when return value is an object with err property, caller can see
// that that particular sendToDevice failed, can see the userID and the error
return {err, user: userID};
});
}
} else {
return {response: null, user: userID};
}
});
}));
});
Changes:
Move require() out of the loop. No reason to call it multiple times.
Use .map() to collect the array of promises for Promise.all().
Use Object.keys() to get an array of userIDs from the object keys so we can then use .map() on it.
Use .then() with .once().
Log sendToDevice() error.
Use Promise.all() to track when all the promises are done
Make sure all promise return paths return an object with some common properties so the caller can get a full look at what happened for each user
These are not two problems: the warning you get is trying to help you solve the second problem you noticed.
And the problem is: in Javascript, only functions create separate scopes - every function you define inside a loop - uses the same scope. And that means they don't get their own copies of the relevant loop variables, they share a single reference (which, by the time the first promise is resolved, will be equal to the last element of the array).
Just replace for with .forEach.

How to avoid duplicates populating array with async service

I have a component that fetches content from a service to process it. The thing is I can have multiple calls to this function, which results in duplicates on my array. I the following workaround:
getOptions(): Observable<PickQuality[]> {
console.log("MY LENGTH: ", this.options.length) // <=== Always returns 0 because the callback hasn't run yet
if(this.options.length == 0) {
this.championService.getChampions()
.subscribe(champions => {
champions.forEach(champion => this.options.push(new PickQuality(champion, 0)));
this.reevaluate();
this.optionsSubject.next(this.options);
});
return this.optionsSubject.asObservable();
}
else
return Observable.of(this.options);
}
and it didn't work, and then I tried the following trick inside the callback (where the this.options.length is correctly recognized):
if(this.options.length != 0) return; // <=== Looks bad!
which actually worked but seemed extremely inefficient to me, since the call to my service is still executed. How can I fix this?
I'd recommend to restructure your code a little:
if (this.options.length == 0) {
let source = this.championService.getChampions()
.share();
source.subscribe(champions => {
// ... whatever
this.options = whateverResult;
});
return source;
} else {
return Observable.of(this.options);
}
Now you can avoid using Subjects and return the source Observable which represents the HTTP request and is shared via the share() operator. This means there's only one HTTP request and its result is sent to this internal subscribe() call as well as to the subscriber outside this method.
Check for duplicates before pushing them.
this.championService.getChampions()
.subscribe(champions => {
champions.forEach(champion => {
if (champions.indexOf(champion) == -1)
this.options.push(new PickQuality(champion, 0));
});
this.reevaluate();
this.optionsSubject.next(this.options);
});

How to promisify this Mongoose code?

I am trying to use Mongoose's built in promise support to write some clean Javascript code for a user sending a friend request to another. However, when I try to ensure proper error handling and sequentiality, I still end up with a (slightly smaller than normal) pyramid of doom.
Here, I first ensure that the friend request is valid, then save the target's Id to the requester's sent requests then, if that save was successful, save the requester's Id to the target's friend requests.
Do I need to use a third party library like q in order to do this as cleanly as possible? How can I structure this such that I can use the traditional single error handler at the end?
function _addFriend (requesterId, targetId) {
// (integer, integer)
User.findById(requesterId)
.exec((requester) => {
if (!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests)) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
requester.save()
.then((err) => {
if (err) throw err;
User.findById(targetId)
.exec((err, target) => {
if (err) throw err;
target.friendRequests = target.friendRequests.concat([requesterId])
target.save().then(err => {if (err) throw err})
})
})
}
})
}
You will need some nesting to do conditionals in promise code, but not as much as with callback-based code.
You seem to have messed up a bit of the if (err) throw err; stuff, you should never need that with promises. Just always use .then(result => {…}), and don't pass callbacks to exec any more.
If you always properly return promises from your asynchronous functions (including then callbacks for chaining), you can add the single error handler in the end.
function _addFriend (requesterId, targetId) {
// (integer, integer)
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests) {
return;
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
return requester.save().then(() => {
return User.findById(targetId).exec()
}).then(target => {
target.friendRequests = target.friendRequests.concat([requesterId])
return target.save()
});
});
}
_addFriend(…).catch(err => {
…
})
In English, the way to do this is to use the promises returned by exec() have then blocks return promises, un-indent, then add then to those. Much easier to say in code...
EDIT thanks (again) to #Bergi for making me read and understand the app logic. #Bergi is right that there must be a little nesting to get the job done, but the real point isn't about reducing nesting, but about improving clarity.
Better clarity can come from factoring into logical parts, including some that return in promises.
These few functions conceal the promise nesting that's required by the logic. This doesn't specify (because the OP doesn't indicate how the app should handle) what addFriend should return when it refuses to do so due to an existing request...
function _addFriend (requesterId, targetId) {
// note - pass no params to exec(), use it's returned promise
return User.findById(requesterId).exec().then((requester) => {
return canAddFriend(requester, targetId) ? addFriend(requester, targetId) : null;
});
}
function canAddFriend(requester, targetId) {
return requester && targetId &&
!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests);
}
function addFriend(requester, targetId) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
return requester.save().then(() => {
return User.findById(targetId).exec();
}).then((target) => {
target.friendRequests = target.friendRequests.concat([requesterId]);
return target.save();
});
}
Once you realise that .exec() returns a promise, you can :
achieve the desired flattening and make the code more readable.
avoid the need to handle errors amongst the "success" code.
handle errors in a terminal .then() or .catch().
As a bonus you can also (more readily) throw meaningful errors for each of those x in y conditions.
Straightforwardly, you could write :
function _addFriend(requesterId, targetId) {
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); // or just .push()?
return requester.save();
}).then(() => {
return User.findById(targetId).exec().then(target => {
target.friendRequests = target.friendRequests.concat([requesterId]); // or just .push()?
return target.save();
});
});
}
Note the need for returns to control flow.
But you could do even better. As writtten above, the requested stuff could succeed then the target stuff fail, resulting in a db disparity. So what you really want is a db transaction to guarantee that both happen or neither. Mongoose undoubtedly provides for transactions however you can do something client-side to give you something transaction-like with partial benefit.
function _addFriend(requesterId, targetId) {
return Promise.all([User.findById(requesterId).exec(), User.findById(targetId).exec()]).then(([requester, target]) => { // note destructuring
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
target.friendRequests = target.friendRequests.concat([requesterId]);
return requester.save().then(() => {
return target.save();
});
});
}
Here, you could still get the (unlikely) situation that the first save is successful and the second save fails, but at least you have the assurance that absolutely nothing happens unless both the requester and target exist.
In both cases, call as follows :
_addFriend(requesterId, targetId).then(function() {
// do whatever on success
}, function(error) {
// do whatever on error
});
Even if you don't use the error messages in the live environment, they could be very useful when testing/debugging. Please check them - I may have gotten them wrong.

Javascript callback function in blackboard pattern control flow

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)
}

Categories