Complete subscription/promise before moving on - javascript

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

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

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

Using async/await to make sequential (blocking calls) returns too early

Below is simple example to demonstrate why I am trying to do using the fetch API. I am hoping that async fetchAsync() would block till it's returning data (or an exception) but the output shows that it doesn't.
constructor
entering fetchAsync...
** we are here!!! **
leaving fetchAsync.
finish initialization
I have been trying to figure out how to display the finish string (we are here) after finish initialization when my object is done initializing with the content of the file. Isn't await/async suppose to block till it's done?
class A {
filename = "./resources/test.json";
constructor() {
console.log("constructor");
this.fetchAsync(this.filename)
.then( data => this.initialize(data)
).catch(reason => console.log(reason.message))
}
async fetchAsync(filename) {
console.log("entering fetchAsync...");
let data = await (await fetch(filename)).json();
console.log("leaving fetchAsync.");
return data;
}
initialize() {
setTimeout(() => console.log("finish initialization"), 1000)
}
}
let a = new A();
console.log("*** we are here!!! ***");
await doesn't block - that would be very user and application unfriendly - it just waits for the promise to resolve before continuing, and can't be done on the top-level (yet). If you want we are here to display only after initialization is done, then you need to be able to access a promise that resolves once initialization is done, and then call then on it.
You should also ensure initalize returns a Promise so that it can be chained to the outer call on a.
So, you could try something like this:
const dummyRequest = () => new Promise(res => setTimeout(res, 1000, 'response'));
class A {
// filename = "./resources/test.json";
constructor() {
console.log("constructor");
}
startFetch() {
return this.fetchAsync(this.filename || 'foo')
.then(data => this.initialize(data)).catch(reason => console.log(reason.message))
}
async fetchAsync(filename) {
console.log("entering fetchAsync...");
// let data = await (await fetch(filename)).json();
const data = await dummyRequest();
console.log("leaving fetchAsync.");
return data;
}
initialize() {
return new Promise(resolve => {
setTimeout(() => {
console.log("finish initialization");
resolve();
}, 1000);
});
}
}
const a = new A();
a.startFetch()
.then(() => {
console.log("*** we are here!!! ***");
});
Constructors can't be async, which is why I made the startFetch function.
You must be forgetting what asynchronous means:
class A {
constructor() {
this.filename = "./resources/test.json";
console.log("constructor");
this.fetchAsync(this.filename)
.then( data => this.initialize(data)
).catch(reason => console.log(reason.message))
}
async fetchAsync(filename){
console.log("entering fetchAsync...");
let data = await fetch(filename);
console.log("leaving fetchAsync.");
return data.json;
}
initialize() {
setTimeout(() => {console.log("finish initialization"); console.log("*** we are here!!! ***"}, 1000)
}
}
let a = new A();

Repeating call to function based on its callback

I have the following setup:
const foo = () => {
bar((err,payload) => {
// some stuff happens here
}
}
So I need to continuously call "bar" inside of "foo" until a certain outcome happens in the "some stuff happens here" part. But I obviously have to wait for the outcome of the callback before re-calling "bar" - how could I structure this?
Try using the new await and async in ES6.
const foo = async () => {
var myOutcome;
while (/*CHECK HERE*/) {
try {
myOutcome = await /* call async javascript function here */
} catch (e) {
// Handle error here
}
}
}
This all relies on Javascript Promises, so long as that function returns a promise it will automatically be run more of a synchronous like manner.
Edit if the function only supports callbacks you can wrap it in a promise like so.
function myCallbackFunctionPromise() {
return new Promise((resolve, reject) => {
callbackFunction((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}

Fire callback after two separate successful http requests

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

Categories