Fire callback after two separate successful http requests - javascript

Root component of my application on init call two asynchronous functions from my services to get data. I would like to know how to call a function after they are both completed. I am using angular 2.0.0-alpha.44 and typescript 1.7.3
import {Component, OnInit} from 'angular2/angular2';
import {ServiceA} from './services/A';
import {ServiceB} from './services/B';
#Component({
selector: 'app',
template: `<h1>Hello</h1>`
})
export class App {
constructor(
public serviceA: ServiceA,
public serviceB: ServiceB
) { }
onInit() {
// How to run a callback, after
// both getDataA and getDataB are completed?
// I am looing for something similar to jQuery $.when()
this.serviceA.getDataA();
this.serviceB.getDataB();
}
}
serviceA.getDataA and serviceA.getDataB are simple http get functions:
// Part of serviceA
getDataA() {
this.http.get('./some/data.json')
.map(res => res.json())
.subscribe(
data => {
// save res to variable
this.data = data;
},
error => console.log(error),
// The callback here will run after only one
// function is completed. Not what I am looking for.
() => console.log('Completed')
);
}

A simple still parallel solution would be something like:
let serviceStatus = { aDone: false, bDone: false };
let getDataA = (callback: () => void) => {
// do whatver..
callback();
}
let getDataB = (callback: () => void) => {
// do whatver..
callback();
}
let bothDone = () => { console.log("A and B are done!");
let checkServiceStatus = () => {
if ((serviceStatus.aDone && serviceStatus.bDone) == true)
bothDone();
}
getDataA(() => {
serviceStatus.aDone = true;
checkServiceStatus();
});
getDataA(() => {
serviceStatus.bDone = true;
checkServiceStatus();
});
I personally use RxJS to get me out of sticky situations like this, might be worth looking at.
EDIT:
Given feedback that RxJS is actually being used:
let observable1: Rx.Observable<something>;
let observable2: Rx.Observable<something>;
let combinedObservable = Rx.Observable
.zip(
observable1.take(1),
observable2.take(1),
(result1, result2) => {
// you can return whatever you want here
return { result1, result2 };
});
combinedObservable
.subscribe(combinedResult => {
// here both observable1 and observable2 will be done.
});
This example will run both observables in parallel and combine the result into one result when they are both done.

You could pass getDataA and getDataB callbacks in their function definitions, then call whatever you want in order:
function getDataA(callback) {
// do some stuff
callback && callback();
}
function getDataB(callback) {
// do some stuff
callback && callback();
}
function toCallAfterBoth() {
// do some stuff
}
getDataA(getDataB(toCallAfterBoth));

You could nest your function calls.
EG:
function getDataA (callback) {
var dataA = {test:"Hello Data A"};
callback && callback(dataA);
}
function getDataB (callback) {
var dataB = {test:"Hello Data B"};
callback && callback(dataB);
}
getDataA(function (dataA) {
getDataB(function (dataB) {
console.log("Both functions are complete and you have data:");
console.log(dataA);
console.log(dataB);
});
});

Related

Jest Unit Testing function that calls a second one that returns a promise

Edited Question with vazsonyidl suggestions applied
I have to write unit tests for a function similar to this one:
import {External} from 'ExternalModule';
async functionA(){
this.functionB().then((data) => {
External.functionC(options);
console.log("Reached1");
}).catch((data) => {
const { OnError = "" } = data || {}
if(OnError) {
External.functionC(anotherOptions);
console.log("Reached2");
}
})
}
functionB() {
return new Promise(() => {
});
}
As functionC belongs to another module, I placed a mock of it in the _mocks_folder:
//_mocks_/ExternalModule.ts
export var External: ExternalClass = {
functionC(){}
}
class ExternalClass{
constructor(){};
functionC(){};
}
I have mocked functionB in two diferent ways for testing the then and the catch :
it("should test then block", () => {
functionB = jest.fn(() => {return Promise.resolve()});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
it("should test catch block", () => {
const err = { OnError: "Error" };
functionB = jest.fn(() => {return Promise.reject(err)});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
What I am trying to do is expect that functionC was called and called with the correct params, but the test is always passing even if I test if functionC was not called.
What am I doing wrong?
Jest does not wait for the async code to complete before doing assertions.
You can use the following function:
const waitForPromises = () => new Promise(setImmediate);
to force Jest to wait for promises to complete before continuing like so:
it("does something", async () => {
promiseCall();
await waitForPromises();
expect(something).toBe(something)
});
I think when this function catch error, this error should have an 'OnError' property so the functionC can run.
const { OnError = "" } = data || {}
if(OnError) {
ExternalClass.functionC(anotherOptions);
}
change you response error data to return Promise.reject({OnError: '404'}) may solve this problem.
Because you are not providing it to your class.
The following code is working for me:
class A {
async functionA() {
this.functionB().then((data) => {
this.functionC(); // It woll log aaa here, you need this one.
}).catch((data) => {
const {OnError = ''} = data || {};
if (OnError) {
console.log('onerror');
}
});
}
functionB() {
return new Promise(() => {
});
}
functionC() {
return 2;
}
}
describe('a', () => {
it('test', () => {
const a = new A();
a.functionB = jest.fn(() => Promise.resolve());
const functionBSpy = jest.spyOn(a, 'functionC');
void a.functionA().then(() => {
expect(functionBSpy).toHaveBeenCalledTimes(1);
});
});
});
Hope this helps, any comment appreciated.
As you provided no information about your functionB I mocked something that may suitable for you.
Your original problem is that Jest does not wait for your callbacks to settle. It does the assertion although, even if your function calls happen later, Jest will not recognise them and says that no call ever occurred.
There are several docs available, for example Jest's one here

Complete subscription/promise before moving on

I need to check if a thing exists before anything else happens. I listen for input via the eventBus like so:
eventBus.on('element.updateId', (event) => {
const b = async ()=> {
return await this.checkNewId(event.newId).then()
}
return b();
});
and this is the service function that needs to complete, but it doesn't complete instead it moves on to other functions - what am i doing wrong?
checkNewId(newId) {
return this.myService.getDiagram(newId).pipe(
tap(
(data) => {
this.validationErrors.next('Duplicate');
return data;
},
(err) => {
this.validationErrors.next('');
}
),
first()
).toPromise();
}
I tried to follow this example here: Subscription to promise

How do I setup this JS code to do better testing?

Hi guys I'm having trouble testing the below JS using Jest. It starts with waitForWorker. if the response is 'working' then it calls waitForWorker() again. I tried Jest testing but I don't know how to test an inner function call and I've been researching and failing.
const $ = require('jquery')
const axios = require('axios')
let workerComplete = () => {
window.location.reload()
}
async function checkWorkerStatus() {
const worker_id = $(".worker-waiter").data('worker-id')
const response = await axios.get(`/v1/workers/${worker_id}`)
return response.data
}
function waitForWorker() {
if (!$('.worker-waiter').length) {
return
}
checkWorkerStatus().then(data => {
// delay next action by 1 second e.g. calling api again
return new Promise(resolve => setTimeout(() => resolve(data), 1000));
}).then(worker_response => {
const working_statuses = ['queued', 'working']
if (worker_response && working_statuses.includes(worker_response.status)) {
waitForWorker()
} else {
workerComplete()
}
})
}
export {
waitForWorker,
checkWorkerStatus,
workerComplete
}
if (process.env.NODE_ENV !== 'test') $(waitForWorker)
Some of my test is below since i can't double check with anyone. I don't know if calling await Worker.checkWorkerStatus() twice in the tests is the best way since waitForWorker should call it again if the response data.status is 'working'
import axios from 'axios'
import * as Worker from 'worker_waiter'
jest.mock('axios')
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: { reload: jest.fn() }
})
});
beforeEach(() => jest.resetAllMocks() )
afterEach(() => {
jest.restoreAllMocks();
});
describe('worker is complete after 2 API calls a', () => {
const worker_id = Math.random().toString(36).slice(-5) // random string
beforeEach(() => {
axios.get
.mockResolvedValueOnce({ data: { status: 'working' } })
.mockResolvedValueOnce({ data: { status: 'complete' } })
jest.spyOn(Worker, 'waitForWorker')
jest.spyOn(Worker, 'checkWorkerStatus')
document.body.innerHTML = `<div class="worker-waiter" data-worker-id="${worker_id}"></div>`
})
it('polls the correct endpoint twice a', async() => {
const endpoint = `/v1/workers/${worker_id}`
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint]])
expect(data).toMatchObject({"status": "working"})
})
await Worker.checkWorkerStatus().then((data) => {
expect(axios.get.mock.calls).toMatchObject([[endpoint],[endpoint]])
expect(data).toMatchObject({"status": "complete"})
})
})
it('polls the correct endpoint twice b', async() => {
jest.mock('waitForWorker', () => {
expect(Worker.checkWorkerStatus).toBeCalled()
})
expect(Worker.waitForWorker).toHaveBeenCalledTimes(2)
await Worker.waitForWorker()
})
I think there are a couple things you can do here.
Inject status handlers
You could make the waitForWorker dependencies and side effects more explicit by injecting them into the function this lets you fully black box the system under test and assert the proper injected effects are triggered. This is known as dependency injection.
function waitForWorker(onComplete, onBusy) {
// instead of calling waitForWorker call onBusy.
// instead of calling workerComplete call onComplete.
}
Now to test, you really just need to create mock functions.
const onComplete = jest.fn();
const onBusy = jest.fn();
And assert that those are being called in the way you expect. This function is also async so you need to make sure your jest test is aware of the completion. I notice you are using async in your test, but your current function doesnt return a pending promise so the test will complete synchronously.
Return a promise
You could just return a promise and test for its competition. Right now the promise you have is not exposed outside of waitForWorker.
async function waitForWorker() {
let result = { status: 'empty' };
if (!$('.worker-waiter').length) {
return result;
}
try {
const working_statuses = ['queued', 'working'];
const data = await checkWorkerStatus();
if (data && working_statuses.includes(data.status)) {
await waitForWorker();
} else {
result = { status: 'complete' };
}
} catch (e) {
result = { status: 'error' };
}
return result;
}
The above example converts your function to async for readability and removes side effects. I returned an async result with a status, this is usefull since there are many branches that waitForWorker can complete. This will tell you that given your axios setup that the promise will complete eventually with some status. You can then use coverage reports to make sure the branches you care about were executed without worrying about testing inner implementation details.
If you do want to test inner implementation details, you may want to incorporate some of the injection principals I mentioned above.
async function waitForWorker(request) {
// ...
try {
const working_statuses = ['queued', 'working'];
const data = await request();
} catch (e) {
// ...
}
// ...
}
You can then inject any function into this, even a mock and make sure its called the way you want without having to mock up axios. In your application you simply just inject checkWorkerStatus.
const result = await waitForWorker(checkWorkerStatus);
if (result.status === 'complete') {
workerComplete();
}

Function wrapper for another functions

I have found that a lot of my API calls functions change loading property to true in the beginning and to false after it's finished. The thing is I have a lot of functions and I like to keep my code DRY.
So, I came up with something like this:
async loadingWrap (func, ...args) {
this.loading = true
await func(...args)
this.loading = false
}
and when I call it is like this:
await this.loadingWrap(
this.someAsyncFunction, { param: 'Value' }
)
where what ideally I want would be:
await this.loadingWrap(this.someAsyncFunction({ param: 'Value'}))
so it will look like a normal function to the future reader (me or someone else).
Is that possible? I looked at higher-order functions, but no luck so far.
You can almost get what you want. What you need to do is to not pass the arguments and call the function without any arguments. This behaves similarly to native functions that accept callbacks like setTimeout() or addEventListener():
async loadingWrap (func) {
this.loading = true
await func()
this.loading = false
}
Then call it similar to how you'd call functions like setTimeout():
await this.loadingWrap(() => this.someAsyncFunction({ param: 'Value'}))
The trick is to wrap your function in an anonymous function that accepts no arguments - just like other functions like it in the js world.
Here's a full working demo with console.log replacing the loading variable:
async function loadingWrap (func) {
console.log('loading');
await func()
console.log('done loading');
}
function timer (x) {
return new Promise((ok,fail) => setTimeout(ok,x));
}
async function test () {
console.log('calling async function');
await loadingWrap(() => timer(2000));
console.log('finished calling async function');
}
test();
From what you want:
await this.loadingWrap(this.someAsyncFunction({ param: 'Value'}))
This won't work because it will treat the parameter as a nested function.
The order of operations will be:
Call this.someAsyncFunction({ param: 'Value'})
Call this.loadingWrap(x) where x is the return value of step 1
This type of function evaluation is exactly like mathematical functions, where to evaluate f(g(x)) (f of g, given x), you first evaluate g given the value x, and then use the result to evaluate f.
A possible solution...
You might be able to use JavaScript's Proxy object. As the docs say, you can use them on a function by using the apply trap.
You'll write your handler generically to handle any function trying to use a loading flag.
const handler = {
apply: async function(target, thisArg, argumentsList) {
thisArg.loading = true
await target.apply(thisArg, argumentsList)
thisArg.loading = false
}
}
You will then create your someAsyncFunction member function by creating the proxy like this:
YourClass.prototype.someAsyncFunction = new Proxy(someAsyncFunction, handler);
Then you call it like this:
// inside some other async member function...
await this.someAsyncFunction({ param: 'Value'})
Here is a run-able example (there's nothing on the page, just console output):
class MyObj {
constructor() {
this.loading = false
}
async someAsyncFunction(val) {
console.log(`entering someAsyncFunction: loading = ${this.loading}`)
console.log(`calling this.asyncLoad...`)
await this.asyncLoad({
value: val
})
console.log(`exiting someAsyncFunction: loading = ${this.loading}`)
}
}
async function asyncLoad(params) {
return new Promise(resolve => {
console.log(`entering asyncLoad: loading = ${this.loading}, value = ${params.value}`)
setTimeout(() => {
console.log(`exiting asyncLoad: loading = ${this.loading}, value = ${params.value}`)
resolve()
}, 1000)
})
}
const handler = {
apply: async function(target, thisArg, argumentsList) {
console.log('PROXY: setting load to true...')
thisArg.loading = true
console.log('PROXY: calling the proxied function...')
await target.apply(thisArg, argumentsList)
console.log('PROXY: setting load to false...')
thisArg.loading = false
}
}
MyObj.prototype.asyncLoad = new Proxy(asyncLoad, handler);
async function run() {
let myobj = new MyObj()
console.log(`in run, before calling someAsyncFunction, loading = ${myobj.loading}`)
setTimeout(() => {
console.log(`INTERRUPT: checking loading is true (${myobj.loading})`)
}, 500)
await myobj.someAsyncFunction(1)
console.log(`in run, after calling someAsyncFunction, loading = ${myobj.loading}`)
}
run()
Selective Proxy-ing
If the function you're trying to call is generic enough that you only need to perform Proxy actions sometimes, this is entirely do-able. This is also where Proxy becomes really cool, because you can create different proxies to perform different actions while maintaining the same base code.
In the example below, asyncLoad is my generic function, and I can call it providing an instance of ObjWithoutStatus as the function's this context. But I also created two proxies, one to set the loading status, and another to set the loaderIsRunning status. Each of these end up calling the base function, without having to perform the gymnastics of creating wrappers that maintain the correct scope.
class ObjWithoutStatus {
constructor() {}
}
class ObjWithLoading {
constructor() {
this.loading = false
}
}
class ObjWithLoaderIsRunning {
constructor() {
this.loaderIsRunning = false
}
}
async function asyncLoad(params) {
return new Promise(resolve => {
console.log(`entering asyncLoad: loading = ${this.loading}, value = ${params.value}`)
setTimeout(() => {
console.log(`exiting asyncLoad: loading = ${this.loading}, value = ${params.value}`)
resolve()
}, 1000)
})
}
const handler_loading = {
apply: async function(target, thisArg, argumentsList) {
console.log('PROXY_loading: setting load to true...')
thisArg.loading = true
console.log('PROXY_loading: calling the proxied function...')
await target.apply(thisArg, argumentsList)
console.log('PROXY_loading: setting load to false...')
thisArg.loading = false
}
}
const handler_loaderIsRunning = {
apply: async function(target, thisArg, argumentsList) {
console.log('PROXY_loaderIsRunning: setting load to true...')
thisArg.loaderIsRunning = true
console.log('PROXY_loaderIsRunning: calling the proxied function...')
await target.apply(thisArg, argumentsList)
console.log('PROXY_loaderIsRunning: setting load to false...')
thisArg.loaderIsRunning = false
}
}
const asyncLoad_loading = new Proxy(asyncLoad, handler_loading)
const asyncLoad_loaderIsRunning = new Proxy(asyncLoad, handler_loaderIsRunning)
const x = new ObjWithoutStatus()
const y = new ObjWithLoading()
const z = new ObjWithLoaderIsRunning()
async function run() {
console.log(`in run, before calling asyncLoad, x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`)
setTimeout(() => console.log(`INTERRUPT_asyncLoad: x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`), 500)
await asyncLoad.call(x, {
value: 1
})
console.log(`in run, after calling asyncLoad, x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`)
console.log(`in run, before calling asyncLoad_loading, y.loading = ${y.loading}`)
setTimeout(() => console.log(`INTERRUPT_asyncLoad_loading: y.loading = ${y.loading}`), 500)
await asyncLoad_loading.call(y, {
value: 2
})
console.log(`in run, after calling asyncLoad_loading, y.loading = ${y.loading}`)
console.log(`in run, before calling asyncLoad_loaderIsRunning, z.loaderIsRunning = ${z.loaderIsRunning}`)
setTimeout(() => console.log(`INTERRUPT_asyncLoad_loading: z.loaderIsRunning = ${z.loaderIsRunning}`), 500)
await asyncLoad_loaderIsRunning.call(z, {
value: 3
})
console.log(`in run, after calling asyncLoad_loaderIsRunning, z.loaderIsRunning = ${z.loaderIsRunning}`)
}
run()
It is possible, but you need to make sure the this value is correctly set when the actual function is called:
async loadingWrap (func, thisArg, ...args) {
this.loading = true;
// support functions that resolve to something useful. And provide `this`
let result = await func.apply(thisArg, args);
this.loading = false
return result;
}
And in some async method:
let result = await this.loadingWrap(
this.someAsyncFunction, this, { param: 'Value' }
);
console.log(result);
If you don't like the extra parameter, then you must pass a callback function that sets the this binding correctly, and then you might as well settle the arguments at the same time:
async loadingWrap (func) {
this.loading = true;
let result = await func();
this.loading = false
return result;
}
And in some async method, note the callback function:
let result = await this.loadingWrap(
() => this.someAsyncFunction({ param: 'Value' })
);
console.log(result);
You're looking for a higher order function, which is just a function that returns a function. Lodash uses techniques like this for functions like throttle or debounce.
// Wrap your function in another function that sets the loading property.
// We're passing "this" as "that" to preserve it when loadStuff is called.
function loadingWrap(that, functionToWrap) {
return async function() {
that.loading = true;
let returnVal = await functionToWrap.apply(that, arguments);
that.loading = false;
return returnVal;
}
}
// In your API class
public loadStuff1 = loadingWrap(this, (arg1, arg2) => {
// This method must return a promise for the loading wrap to work.
return http.get('someURL', arg1, arg2);
});
// In the class that uses your api
myAPI.loadStuff1('abc', 123);
Consider using wrapper like this:
function bar(fn) {
console.log('before');
fn();
console.log('after');
}
function baz(...params) {
console.log('inside', params);
}
bar(() => baz(1, 2, 3));
class A {
constructor() {
this.loading = false;
}
async loadable(fn) {
this.loading = true;
await fn();
this.loading = false;
}
async load() {
return new Promise(res => setTimeout(res, 2000))
}
async fetch() {
this.loadable(this.load); // or () => this.load(params)
}
}
new A().fetch();

Inconsitant mergeMap behaviour

I am currently working on a file uploading method which requires me to limit the number of concurrent requests coming through.
I've begun by writing a prototype to how it should be handled
const items = Array.from({ length: 50 }).map((_, n) => n);
from(items)
.pipe(
mergeMap(n => {
return of(n).pipe(delay(2000));
}, 5)
)
.subscribe(n => {
console.log(n);
});
And it did work, however as soon as I swapped out the of with the actual call. It only processes one chunk, so let's say 5 out of 20 files
from(files)
.pipe(mergeMap(handleFile, 5))
.subscribe(console.log);
The handleFile function returns a call to my custom ajax implementation
import { Observable, Subscriber } from 'rxjs';
import axios from 'axios';
const { CancelToken } = axios;
class AjaxSubscriber extends Subscriber {
constructor(destination, settings) {
super(destination);
this.send(settings);
}
send(settings) {
const cancelToken = new CancelToken(cancel => {
// An executor function receives a cancel function as a parameter
this.cancel = cancel;
});
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.next([null, resp.data]))
.catch(e => this.next([e, null]));
}
next(config) {
this.done = true;
const { destination } = this;
destination.next(config);
}
unsubscribe() {
if (this.cancel) {
this.cancel();
}
super.unsubscribe();
}
}
export class AjaxObservable extends Observable {
static create(settings) {
return new AjaxObservable(settings);
}
constructor(settings) {
super();
this.settings = settings;
}
_subscribe(subscriber) {
return new AjaxSubscriber(subscriber, this.settings);
}
}
So it looks something like this like
function handleFile() {
return AjaxObservable.create({
url: "https://jsonplaceholder.typicode.com/todos/1"
});
}
CodeSandbox
If I remove the concurrency parameter from the merge map function everything works fine, but it uploads all files all at once. Is there any way to fix this?
Turns out the problem was me not calling complete() method inside AjaxSubscriber, so I modified the code to:
pass(response) {
this.next(response);
this.complete();
}
And from axios call:
axios(Object.assign({ cancelToken }, settings))
.then(resp => this.pass([null, resp.data]))
.catch(e => this.pass([e, null]));

Categories