Related
I try to implement a loop in my noejs app that will always wait between the tasks. For this I found the setInterval function and I thought it is the solution for me. But as I found out, the first Interval, means the very first action also wait until the interval is ready. But I want that the first action runs immediatly and then each action with the given interval.
In arry scope:
myArray[0] starts immediatly while myArray[1..10] will start with Interval waiting time.
I tried it with:
function rollDice(profilearray, browserarray, url) {
return new Promise((resolve, reject) => {
var i = 0;
const intervalId = setInterval(
(function exampleFunction() {
console.log(profilearray[i].profil);
//########################################################################
createTestCafe("localhost", 1337, 1338, void 0, true)
.then((tc) => {
testcafe = tc;
runner = testcafe.createRunner();
inputStore.metaUrl = url;
inputStore.metaLogin = teamdataarray[0].email;
inputStore.metaPassword = teamdataarray[0].password;
inputStore.moderator = profilearray[i].profil;
inputStore.message = profilearray[i].template;
inputStore.channelid = profilearray[i].channelid;
})
.then(() => {
return runner
.src([__basedir + "/tests/temp.js"])
.browsers(browserarray)
.screenshots("", false)
.run()
.then((failedCount) => {
testcafe.close();
if (failedCount > 0) {
console.log(profilearray[i].profil);
console.log("No Success. Fails: " + failedCount);
//clearInterval(intervalId);
//reject("Error");
} else {
console.log(profilearray[i].profil);
console.log("All success");
//clearInterval(intervalId);
//resolve("Fertig");
}
});
})
.catch((error) => {
testcafe.close();
console.log(profilearray[i].profil);
console.log("Testcafe Error" + error);
//clearInterval(intervalId);
//reject("Error");
});
//######################################################################
i++;
console.log("Counter " + i);
if (i === profilearray.length) {
clearInterval(intervalId);
resolve("Fertig");
}
return exampleFunction;
})(),
3000
); //15 * 60 * 1000 max time to wait (user input)
});
}
The way I have done works bad because in the first action it will not start the testcafe. But in all other actions it will do.
Anybody knows a better way to do this?
Scope:
Give a array of data and for each array start testcafe with a given waiting time. 3 seconds up to 15 minutes. Because in some cases 15 Minutes is a long time I want to start the first one without any waiting time.
Iam open for any suggestion
For modern JavaScript await and async should be used instead of then and catch.
This will make many things easier, and the code becomes more readable. You e.g. can use a regular for loop to iterate over an array while executing asynchronous tasks within it. And use try-catch blocks in the same way as you would in synchronous code.
// a helperfunction that creates a Promise that resolves after
// x milliseconds
function wait(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function rollDice(profilearray, browserarray, url) {
for (let i = 0; i < profilearray.length; i++) {
// depending how you want to handle the wait you would create
// the "wait"-Promise here
// let timer = wait(3000)
let testcafe = await createTestCafe("localhost", 1337, 1338, void 0, true);
try {
let runner = testcafe.createRunner();
inputStore.metaUrl = url;
inputStore.metaLogin = teamdataarray[0].email;
inputStore.metaPassword = teamdataarray[0].password;
inputStore.moderator = profilearray[i].profil;
inputStore.message = profilearray[i].template;
inputStore.channelid = profilearray[i].channelid;
let failedCount = await runner.src([__basedir + "/tests/temp.js"])
.browsers(browserarray)
.screenshots("", false)
.run()
if (failedCount > 0) {
// ...
} else {
// ...
}
} catch (err) {
console.log(profilearray[i].profil);
console.log("Testcafe Error" + error);
} finally {
testcafe.close();
}
// Here you would wait for the "wait"-Promise to resolve:
// await timer;
// This would have similar behavior to an interval.
// Or you wait here for a certain amount of time.
// The difference is whether you want that the time the
// runner requires to run counts to the waiting time or not.
await wait(3000)
}
return "Fertig"
}
Declare function before setInterval, run setInterval(exampleFunction, time) and then run the function as usual (exampleFunction()). May not be ideal to the millisecond, but if you don't need to be perfectly precise, should work fine.
Ask if you need further assistance
EDIT: now looking twice at it, why are you calling the function you pass as parameter to setInterval inside the setInterval?
I need to call an api to get a status every 2 seconds if the response is running and first return when response is either complete or failed, or until 30 seconds have passed and the function times out.
This is what I have now which works, but I am sure it can be done much more efficient, but I simply can't figure it out at this point:
const getStatus = async (processId) => {
try {
const response = await fetch(`example.com/api/getStatus/${processId}`);
const status = await response.json();
return await status;
} catch(err) {
// handle error
}
}
Inside another async function using getStatus():
randomFunction = async () => {
let status = null;
let tries = 0;
let stop = false;
while (tries <= 15 && !stop) {
try {
status = await getStatus('some-process-id');
if (status === 'complete') {
stop = true;
// do something outside of loop
}
if (status === 'failed') {
stop = true;
throw Error(status);
}
if (tries === 15) {
stop = true;
throw Error('Request timed out');
}
} catch (err) {
// handle error
}
const delay = time => new Promise(resolve => setTimeout(() => resolve(), time));
if (tries < 15) {
await delay(2000);
}
tries++;
}
}
I would prefer to handle the looping inside getStatus() and in a more readable format, but is it possible?
EDIT:
I tried a solution that looks better and seems to work as I expect, see it here:
https://gist.github.com/AntonBramsen/6cec0faade032dfa3c175b7d291e07bd
Let me know if parts of the solution contains any solutions that are bad practice.
Your question is for javascript. Unfortunately I don't drink coffee, I can only give you the code in C#. But I guess you get the gist and can figure out how to translate this into java
Let's do this as a generic function:
You have a function that is called every TimeSpan, and you want to stop calling this function whenever the function returns true, you want to cancel, whenever some maximum time has passed.
For this maximum time I use a CancellationToken, this allows you to cancel processing for more reasons than timeout. For instance, because the operator wants to close the program.
TapiResult CallApi<TapiResult> <Func<TapiResult> apiCall,
Func<TapiResult, bool> stopCriterion,
CancellationToken cancellationToken)
{
TapiResult apiResult = apiCall;
while (!stopCriterion(apiResult))
{
cancellationToken.ThrowIfCancellationRequested();
Task.Delay(delayTime, cancellationToken).Wait;
apiResult = apiCall;
}
return apiResult;
}
ApiCall is the Api function to call. The return value is a TApiResult. In your case the status is your TApiResult
StopCriterion is a function with input ApiResult and output a boolean that is true when the function must stop. In your case this is when status equals complete or failed
CancellationToken is the Token you can get from a CancellationTokenSource. Whenever you want the procedure to stop processing, just tell the CancellationTokenSource, and the function will stop with a CancellationException
Suppose this is your Api:
Status MyApiCall(int x, string y) {...}
Then the usage is:
Timespan maxProcessTime = TimeSpan.FromSeconds(45);
var cancellationTokenSource = new CancellationTokenSource();
// tell the cancellationTokenSource to stop processing afer maxProcessTime:
cancellationTokenSource.CancelAfter(maxProcessTime);
// Start processing
Status resultAfterProcessing = CallApi<Status>(
() => MyApiCall (3, "Hello World!"), // The Api function to call repeatedly
// it returns a Status
(status) => status == complete // stop criterion: becomes true
|| status == failed, // when status complete or failed
cancellationTokenSource.Token); // get a token from the token source
TODO: add try / catch for CancellationException, and process what should be done if the task cancels
The function will stop as soon as the stopCriterion becomes true, or when the CancellationTokenSource cancels. This will automatically be done after maxTimeOut. However, if you want to stop earlier, for instance because you want to stop the program:
cancellationTokenSource.Cancel();
The situation is simple :
I have a nodejs server (called it API-A) that use Bluebird as a Promise handler.
I have a client(browser) that ask data through the API-A which get the data from another API (API-B). API-B could be a Weather service from example and then API-A aggregate the data with other data and send it back to client.
The situation is the next: API-B need a token with a TTL of 1800 second.
So for each request done by the client, I check if my token is expired or not.
I have this kind of code :
function getActivities()
{
return this.requestAuthorisation()
.then(()=>{
// some other code that is not interesting
})
}
Everything works fine with the glory of promise.
requestAuthorisation() check if the token is valid (next!!!) and if not (I do a request the API-B to refresh the token)
The problem is here:
between the time, the token is expired and the time to obtain a fresh one, some times happen. if 1000 clients ask at the same time these, I will have 1000 request of token to API-B, that is not cool.
How can I avoid that ? I try to avoid a cron-way to do it, to avoid unnecessary call and the problem is the same.
I try to create a sort of global variable (boolean) that track the refreshing status of the token but impossible to find a sort of Promise.WaitFor (variable change)
the Promise.all can not be use because I am in different scope of event.
Is there a way to queue until the token is refresh ?
Please help !
If I understand this write, we need to do two things:
Do not call our refreshToken several times when one is in progress
Once completed, let all the waiting request know that request for the token in completed so that they can continue their work.
If you combine Observable pattern and a state to maintain the in-progress state, this can be done like below
// MyObservable.js:
var util = require('util');
var EventEmitter = require('events').EventEmitter;
let inProgress = false;
function MyObservable() {
EventEmitter.call(this);
}
// This is the function responsible for getting a refresh token
MyObservable.prototype.getToken = function(token) {
console.log('Inside getToken');
if (!inProgress) {
console.log('calling fetchToken');
resultPromise = this.fetchToken();
inProgress = true;
resultPromise.then((result) => {
console.log('Resolved fetch token');
inProgress = false;
this.emit('done', 'token refreshed');
});
}
}
// This is a mock function to simulate the promise based API.
MyObservable.prototype.fetchToken = function(token) {
console.log('Inside fetchToken');
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolving');
resolve("Completed");
}, 2000);
});
}
util.inherits(MyObservable, EventEmitter);
module.exports = MyObservable;
Now we can implement this and observe for the call to complete
const MyObservable = require('./MyObservable');
const observable = new MyObservable();
const A = () => {
console.log('Inside A');
observable.on('done', (message) => {
console.log('Completed A');
});
observable.getToken('test');
}
for (let i = 0; i < 5; i++) {
setTimeout(A, 1000);
}
If we run this code, you will get an output where fetchToeken is called only once even though our method A is called 5 times during the same duration.
Hope this helps!
I've got 2 nested Observable Streams which do HTTP requests. Now I'd like to display a loading indicator, but can't get it working correctly.
var pageStream = Rx.createObservableFunction(_self, 'nextPage')
.startWith(1)
.do(function(pageNumber) {
pendingRequests++;
})
.concatMap(function(pageNumber) {
return MyHTTPService.getPage(pageNumber);
})
.do(function(response) {
pendingRequests--;
});
Rx.createObservableFunction(_self, 'search')
.flatMapLatest(function(e) {
return pageStream;
})
.subscribe();
search();
nextPage(2);
nextPage(3);
search();
This will trigger pendingRequests++ 4 times, but pendingRequests-- only once, because flatMapLatest will cancel the inner observable before the first 3 HTTP responses arrive.
I couldn't find anything like an onCancel callback. I also tried onCompleted and onError, but those too won't get triggered by flatMapLatest.
Is there any other way to get this working?
Thank you!
EDIT: Desired loading indicator behavior
Example: Single search() call.
search() -> start loading indicator
when search() response comes back -> disable loading indicator
Example: search() and nextPage() call. (nextPage() is called before search() response came back.)
search() -> start loading indicator
nextPage() -> indicator is already started, though nothing to do here
stop loading indicator after both responses arrived
Example: search(), search(). (search() calls override each other, though the response of the first one can be dismissed)
search() -> start loading indicator
search() -> indicator is already started, though nothing to do here
stop loading indicator when the response for the second search() arrived
Example: search(), nextPage(), search(). (Again: Because of the second search(), the responses from the previous search() and nextPage() can be ignored)
search() -> start loading indicator
nextPage() -> indicator is already started, though nothing to do here
search() -> indicator is already started, though nothing to do here
stop loading indicator when response for the second search() arrived
Example: search(), nextPage(). But this time nextPage() is called after search() response came back.
search() -> start loading indicator
stop loading indicator because search() response arrived
nextPage() -> start loading indicator
stop loading indicator because nextPage() response arrived
I tried using pendingRequests counter, because I can have multiple relevant requests at the same time (for example: search(), nextPage(), nextPage()). Then of course I'd like to disable the loading indicator after all those relevant requests finished.
When calling search(), search(), the first search() is irrelevant. Same applies for search(), nextPage(), search(). In both cases there's only one active relevant request (the last search()).
With switchMap aka flatMapLatest you want to trim asap execution of the current inner-stream as new outer-items arrive. It is surely a good design decision as otherwise it would bring a lot of confusion and allow some spooky actions. If you really wanted do do something onCancel you can always create your own observable with custom unsubscribe callback. But still I would recommend not to couple unsubscribe with changing state of the external context. Ideally the unsubscribe would only clean up internally used resources.
Nevertheless your particular case can be solved without accessing onCancel or similar. The key observation is - if I understood your use case correctly - that on search all previous / pending actions may be ignored. So instead of worry about decrementing the counter we can simply start counting from 1.
Some remarks about the snippet:
used BehaviorSubject for counting pending requests - as it is ready to be composed with other streams;
checked all cases you posted in your question and they work;
added some fuzzy tests to demonstrate correctness;
not sure if you wanted to allow nextPage when a search is still pending - but it seems to be just a matter of using concatMapTo vs merge;
used only standard Rx operators.
PLNKR
console.clear();
const searchSub = new Rx.Subject(); // trigger search
const nextPageSub = new Rx.Subject(); // triger nextPage
const pendingSub = new Rx.BehaviorSubject(); // counts number of pending requests
const randDurationFactory = min => max => () => Math.random() * (max - min) + min;
const randDuration = randDurationFactory(250)(750);
const addToPending = n => () => pendingSub.next(pendingSub.value + n);
const inc = addToPending(1);
const dec = addToPending(-1);
const fakeSearch = (x) => Rx.Observable.of(x)
.do(() => console.log(`SEARCH-START: ${x}`))
.flatMap(() =>
Rx.Observable.timer(randDuration())
.do(() => console.log(`SEARCH-SUCCESS: ${x}`)))
const fakeNextPage = (x) => Rx.Observable.of(x)
.do(() => console.log(`NEXT-PAGE-START: ${x}`))
.flatMap(() =>
Rx.Observable.timer(randDuration())
.do(() => console.log(`NEXT-PAGE-SUCCESS: ${x}`)))
// subscribes
searchSub
.do(() => console.warn('NEW_SEARCH'))
.do(() => pendingSub.next(1)) // new search -- ingore current state
.switchMap(
(x) => fakeSearch(x)
.do(dec) // search ended
.concatMapTo(nextPageSub // if you wanted to block nextPage when search still pending
// .merge(nextPageSub // if you wanted to allow nextPage when search still pending
.do(inc) // nexpage started
.flatMap(fakeNextPage) // optionally switchMap
.do(dec) // nextpage ended
)
).subscribe();
pendingSub
.filter(x => x !== undefined) // behavior-value initially not defined
.subscribe(n => console.log('PENDING-REQUESTS', n))
// TEST
const test = () => {
searchSub.next('s1');
nextPageSub.next('p1');
nextPageSub.next('p2');
setTimeout(() => searchSub.next('s2'), 200)
}
// test();
// FUZZY-TEST
const COUNTER_MAX = 50;
const randInterval = randDurationFactory(10)(350);
let counter = 0;
const fuzzyTest = () => {
if (counter % 10 === 0) {
searchSub.next('s' + counter++)
}
nextPageSub.next('p' + counter++);
if (counter < COUNTER_MAX) setTimeout(fuzzyTest, randInterval());
}
fuzzyTest()
<script src="https://npmcdn.com/rxjs#5.0.0-beta.11/bundles/Rx.umd.js"></script>
One way: use the finally operator (rxjs4 docs, rxjs5 source). Finally triggers whenever the observable is unsubscribed or completes for any reason.
I'd also move the counter logic to inside the concatMap function since you are really counting the getPage requests, not the number of values that have gone through. Its a subtle difference.
var pageStream = Rx.createObservableFunction(_self, 'nextPage')
.startWith(1)
.concatMap(function(pageNumber) {
++pendingRequests;
// assumes getPage returns an Observable and not a Promise
return MyHTTPService.getPage(pageNumber)
.finally(function () { --pendingRequests; })
});
I wrote a solution for your problem from scratch.
For sure it might be written in a more functional way but it works anyway.
This solution is based on reqStack which contains all requests (keeping the call order) where a request is an object with id, done and type properties.
When the request is done then requestEnd method is called.
There are two conditions and at least one of them is enough to hide a loader.
When the last request on the stack was a search request then we can hide a loader.
Otherwise, all other requests have to be already done.
function getInstance() {
return {
loaderVisible: false,
reqStack: [],
requestStart: function (req){
console.log('%s%s req start', req.type, req.id)
if(_.filter(this.reqStack, r => r.done == false).length > 0 && !this.loaderVisible){
this.loaderVisible = true
console.log('loader visible')
}
},
requestEnd: function (req, body, delay){
console.log('%s%s req end (took %sms), body: %s', req.type, req.id, delay, body)
if(req === this.reqStack[this.reqStack.length-1] && req.type == 'search'){
this.hideLoader(req)
return true
} else if(_.filter(this.reqStack, r => r.done == true).length == this.reqStack.length && this.loaderVisible){
this.hideLoader(req)
return true
}
return false
},
hideLoader: function(req){
this.loaderVisible = false
console.log('loader hidden (after %s%s request)', req.type, req.id)
},
getPage: function (req, delay) {
this.requestStart(req)
return Rx.Observable
.fromPromise(Promise.resolve("<body>" + Math.random() + "</body>"))
.delay(delay)
},
search: function (id, delay){
var req = {id: id, done: false, type: 'search'}
this.reqStack.push(req)
return this.getPage(req, delay).map(body => {
_.find(this.reqStack, r => r.id == id && r.type == 'search').done = true
return this.requestEnd(req, body, delay)
})
},
nextPage: function (id, delay){
var req = {id: id, done: false, type: 'nextPage'}
this.reqStack.push(req)
return this.getPage(req, delay).map(body => {
_.find(this.reqStack, r => r.id == id && r.type == 'nextPage').done = true
return this.requestEnd(req, body, delay)
})
},
}
}
Unit tests in Moca:
describe('animation loader test:', function() {
var sut
beforeEach(function() {
sut = getInstance()
})
it('search', function (done) {
sut.search('1', 10).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, nextPage', function (done) {
sut.search('1', 50).subscribe(expectDidHideLoader)
sut.nextPage('1', 20).subscribe(expectDidNOTHideLoader)
testDone(done)
})
it('search, nextPage, nextPage', function(done) {
sut.search('1', 50).subscribe(expectDidHideLoader)
sut.nextPage('1', 40).subscribe(expectDidNOTHideLoader)
sut.nextPage('2', 30).subscribe(expectDidNOTHideLoader)
testDone(done)
})
it('search, nextPage, nextPage - reverse', function(done) {
sut.search('1', 30).subscribe(expectDidNOTHideLoader)
sut.nextPage('1', 40).subscribe(expectDidNOTHideLoader)
sut.nextPage('2', 50).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, search', function (done) {
sut.search('1', 60).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
sut.search('2', 50).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, search - reverse', function (done) {
sut.search('1', 40).subscribe(expectDidNOTHideLoader)
sut.search('2', 50).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, nextPage, search', function (done) {
sut.search('1', 40).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
sut.nextPage('1', 30).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
sut.search('2', 10).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, nextPage (call after response from search)', function (done) {
sut.search('1', 10).subscribe(result => {
expectDidHideLoader(result)
sut.nextPage('1', 10).subscribe(expectDidHideLoader)
})
testDone(done)
})
function expectDidNOTHideLoader(result){
expect(result).to.be.false
}
function expectDidHideLoader(result){
expect(result).to.be.true
}
function testDone(done){
setTimeout(function(){
done()
}, 200)
}
})
Part of the output:
JSFiddle is here.
I think there's a much simpler solution, to explain it I'd like to "re-phrase" the examples you gave in your edit:
The status is "pending" as long as there are un-closed requests.
A response closes all previous requests.
Or, in stream/marbles fashion
(O = request [open], C = response [close], p = pending, x = not pending)
http stream: ------O---O---O---C---O---C---O---O---C---O---
------ Status: x----P--------------x---P----x----P---------x---P---
You can see that the count doesn't matter, we have a flag which is actually on (pending) or off (a response was returned). This true because of you switchMap/flatMap, or as you said at the end of your edit, at each time there's only one active request.
The flag is actually a boolean observable/oberver or simply a subject.
So, what you need is to first define:
var hasPending: Subject<boolean> = BehaviorSubject(false);
The BehaviorSubject is relevant for 2 reasons:
You can set an initial value (false = nothing pending).
New subscribers get the last value, hence, even components that are created later will know if you have a pending request.
Than the rest becomes simple, whenever you send out a request, set the pending to 'true', when a request is done, set the pending flag to 'false'.
var pageStream = Rx.createObservableFunction(_self, 'nextPage')
.startWith(1)
.do(function(pageNumber) {
hasPending.next(true);
})
.concatMap(function(pageNumber) {
return MyHTTPService.getPage(pageNumber);
})
.do(function(response) {
hasPending.next(false);
});
Rx.createObservableFunction(_self, 'search')
.flatMapLatest(function(e) {
return pageStream;
})
.subscribe();
This is rxjs 5 syntax, for rxjs 4 use onNext(...)
If you don't need your flat as an observable, just a value, just declare:
var hasPending: booolean = false;
Then in the .do before the http call do
hasPending = true;
and in the .do after the http call do
hasPending = false;
And that's it :-)
Btw, after re-reading everything, you can test this by an even simpler (though somewhat quick and dirty) solution:
Change your post http 'do' call to:
.do(function(response) {
pendingRequests = 0;
});
Some people are going to think this is too trivial to look for modules or almost put it in a leftPad category perhaps, but there are so many things in npm I have a feeling these two requirements are covered and so am interested to see what other people have done.
I inherited this codebase that has this sort of long updating function with a number of steps. It has sort of a custom locking flag that is half-set up to expire but doesn't actually have the expiration code. Anyway the main thing is to prevent the update from happening while something else is happening. Except sometimes we know we definitely need to update as soon as possible, but not right in the middle of a current update. This stuff is not actually quite working.
So I am looking for one or two modules to do two things:
a lock with an expiration that two different long-running functions (that call other functions with callbacks) can use to make sure they don't step on eachother
a simple module/function that says 'do this function now, or run it again after it finishes its current run'.
Or possibly something that handles both of those might actually make sense in this case, even though from my description it may not be obvious what they have to do with eachother.
The expiration is if there is some case that I don't anticipate that causes the lock not to be removed for a very long time, we don't want to be stuck forever.
Here is an example:
function update() {
// if we are trading, wait until that is done
// if currently updating, skip it or schedule another one right
// after, but only need one more, not a queue of updates
// make a bunch of callbacks or a promise chain, takes a few seconds
// in some cases we will find that we need to do one more update
// immediately after all of this finishes, but not until the end
}
// ...some other places that want to call update
function trade() {
// trade happens sort of sporadically when the tick event fires
// if we are updating, wait until we are done
// make a bunch of callbacks or a promise chain, takes a few seconds
// at the end we need to call update
// but it can't update if it is already updating
}
var Promise = require('bluebird');
// note using a bluebird type of promise
const tradeQueue = [];
var currentlyTrading = false;
var currentlyUpdating = false;
var pendingUpdate = null;
var queuedUpdatePromiseDetails = null;
// returns a promise that returns void, the promise will return
// when either the currently executing update finishes, or a fresh one
// does
function update() {
var test = Math.floor(Math.random() * 1000);
console.log(test, 'update call');
if (!pendingUpdate) {
pendingUpdate = new Promise(function (resolve, reject) {
queuedUpdatePromiseDetails = { resolve: resolve, reject: reject, test: test };
if (currentlyTrading) {
console.log(test, 'setting update to pending');
} else {
// perform update, in callback call resolve() if no err, otherwise reject(err)
console.log(test, 'running update');
currentlyUpdating = true;
setTimeout(resolve, 100);
}
}).finally(function () {
currentlyUpdating = false;
console.log(test, 'update call complete');
pendingUpdate = null;
queuedUpdatePromiseDetails = null;
runPendingTradeOrUpdate();
});
} else {
console.log(test, 'returning existing update promise', queuedUpdatePromiseDetails.test);
}
return pendingUpdate;
}
// returns a promise that completes when this
// new trade finishes
function trade(param) {
var test = Math.floor(Math.random() * 1000);
console.log(test, 'trade call');
return new Promise(function (resolve, reject) {
if (currentlyTrading || currentlyUpdating) {
console.log(test, 'queued trade')
var newTrade = { param: param, resolve: resolve, reject: reject, test: test };
tradeQueue.push(newTrade);
} else {
currentlyTrading = true;
console.log(test, 'beginning trade run');
// perform trade, in callback call resolve() if no error, otherwise reject(err)
setTimeout(resolve, 100);
}
}).finally(function () {
console.log(test, 'trade call complete');
// note that this bit is run for every single trade
currentlyTrading = false;
runPendingTradeOrUpdate();
});
}
// dequeue next trade and run it
function runPendingTradeOrUpdate() {
if (queuedUpdatePromiseDetails && !currentlyUpdating && !currentlyTrading) {
// perform update, in callback call resolve() if no err, otherwise reject(err)
console.log(queuedUpdatePromiseDetails.test, 'running pending update');
currentlyUpdating = true;
setTimeout(queuedUpdatePromiseDetails.resolve, 100);
} else {
if (tradeQueue.length > 0 && !currentlyTrading) {
var nextTrade = tradeQueue.shift();
console.log(nextTrade.test, 'calling queued trade');
trade(nextTrade.param).then(nextTrade.resolve).catch(nextTrade.reject);
}
}
}
var runRandomly = function () {
setTimeout(function () {
update();
}, Math.floor(Math.random() * 300))
setTimeout(function () {
trade(null);
}, Math.floor(Math.random() * 300))
setTimeout(function () {
runRandomly();
}, Math.floor(Math.random() * 300))
}
runRandomly();
OK, thank you so much for your help guys.
This is what I decided I need to do: I created a really simple module now-or-again and am using the existing lock-lock with its maxPending feature instead of an expiration time.
The way I am using those two modules is identical to the examples in the READMEs.