How to throw error after subscription timeout? - rxjs

I'm simply trying to display in the view ( when for example user's connection is too slow and results won't come from subscription) after 30 seconds.
I'm surely missing something because even when results all arrive, the console timeout error message is still appearing.
This is what I tried:
searchInfo() {
this.isLoading = true;
this.info= [];
const source = this.infoService.searchInfo('help');
source.pipe(
timeout(30000),
takeUntil(this.onDestroy$)
).subscribe((infoTable: infoTableContent) => {
this.info.push(infoTable);
this.isLoading = false;
}),
(err) => {
this.isLoading = false;
console.log(err, 'Sorry, ...') //not working
}
I want to be able to display a message like "Sorry, took too long to retrieve data" after 30 seconds of trying to subscribe.
Thank you all in advance

You have an extra ) right after your next callback in the subscribe.
Therefore, your error callback is ignored.
.subscribe((infoTable: infoTableContent) => {
this.info.push(infoTable);
this.isLoading = false;
}), // <-- This paranthesis closes the subscribe method
The following works as expected
.subscribe(
(infoTable: infoTableContent) => {
this.info.push(infoTable);
this.isLoading = false;
},
(err) => {
this.isLoading = false;
console.log(err, 'Sorry, ...');
}
);

When I run this with Mocked Services it runs just fine for me:
const mockService = {
searchInfo: msg => NEVER.pipe(startWith({key: msg}))
};
const onDestroy$ = timer(5000);
let isLoading = true;
const info = [];
mockService.searchInfo('help').pipe(
timeout(1000),
takeUntil(onDestroy$)
).subscribe({
next: (mockTable: any) => {
info.push(mockTable);
isLoading = false;
},
error: err => {
isLoading = false;
console.log('Sorry, ...', err);
}
});
I suspect your bug might be due to miss-formatting (brackets in the wrong place etc)

Related

using https.get instead of got causes a 308

this is a difficult question to ask because I am mystified, but let's see…
I am comparing Got with https.get, and have the following, bare simple code that works. Both Got and https.get return exactly the same result.
But when I use exactly the same code in my Fastify application, Got works as expected but https.get results in a 308.
Is there some way I can debug this code to see what is being sent out by https.get that is causing the remote server to respond with a 308 instead of 200?
import got from 'got';
import https from 'https';
const withGot = async (uri) => {
try {
const json = JSON.parse((await got(uri)).body);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const withHttps = async (uri) => {
try {
const json = await getRequest(uri);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const getRequest = async (uri) => {
return new Promise((resolve) => {
https.get(uri, (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
/**
* Any 2xx status code signals a successful response but
* here we're only checking for 200.
**/
if (statusCode !== 200) {
error = new Error(`ERROR\n${'-'.repeat(50)}\nRequest Failed.\nURI: ${uri}\nStatus Code: ${statusCode}`);
}
else if (!/^application\/json/.test(contentType)) {
error = new Error(`Invalid content-type.\nExpected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
/**
* Consume response data to free up memory
**/
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
}
catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
});
}
const uri = 'https://zenodo.org/api/records/?q=phylogeny';
withGot(uri);
withHttps(uri);
I figured out the reason for the problem (and the solution)… seems like when I use https.get, I still have to pass the options with a port 443 (the default port for https), otherwise, https seems to knock on port 80 and then gets redirected to port 443 which results in the server sending back html which causes the JSON parser to croak. If I pass an options object like below, then it works. But, it is still weird that the standalone script works fine without the options, so I continue to be mystified even though I have found a solution.
const options = {
hostname: 'zenodo.org',
port: 443,
path: `/api/records/?${qs}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};

Vonage browser to browser audio call return some error

I am using Laravel, and trying add browser to browser audio calling. I am using Vonage (Tookbox) API for this, but I am getting some error.
here is my code:
async function audioCall() {
var publisher;
var targetElement = 'publisher';
var pubOptions = {publishAudio:true, publishVideo:false};
publisher = OT.initPublisher(targetElement, pubOptions, function(error) {
if (error) {
alert("The client cannot publish.");
} else {
console.log('Publisher initialized.');
}
});
// Setting an audio source to a new MediaStreamTrack
const stream = await OT.getUserMedia({
videoSource: null
});
const [audioSource] = stream.getAudioTracks();
publisher.setAudioSource(audioSource).then(() => console.log('Audio source updated'));
// Cycling through microphone inputs
let audioInputs;
let currentIndex = 0;
OT.getDevices((err, devices) => {
audioInputs = devices.filter((device) => device.kind === 'audioInput');
// Find the right starting index for cycleMicrophone
audioInputs.forEach((device, idx) => {
if (device.label === publisher.getAudioSource().label) {
currentIndex = idx;
}
});
});
const cycleMicrophone = () => {
currentIndex += 1;
let deviceId = audioInputs[currentIndex % audioInputs.length].deviceId;
publisher.setAudioSource(deviceId);
};
}
This code return an error on console:
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
I believe the issue is that you have
device.kind === 'audioInput'
and I'm pretty sure device.kind comes out like 'audioinput' (all lowercase).
examples:
https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/kind
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices#examples
That would make audioInputs empty (try to console.log it to verify) and gives you the error because there is no device.
Try:
device.kind.toLowerCase() === 'audioinput'
Hope it works out.

NextJS API Route Returns Before Data Received?

I'm not sure what's going on here. I have set up an API route in NextJS that returns before the data has been loaded. Can anyone point out any error here please?
I have this function that calls the data from makeRequest():
export async function getVendors() {
const vendors = await makeRequest(`Vendor.json`);
console.log({ vendors });
return vendors;
}
Then the route: /api/vendors.js
export default async (req, res) => {
const response = await getVendors();
return res.json(response);
};
And this is the makeRequest function:
const makeRequest = async (url) => {
// Get Auth Header
const axiosConfig = await getHeader();
// Intercept Rate Limited API Errors & Retry
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
await new Promise(function (res) {
setTimeout(function () {
res();
}, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
token[n] = null;
originalRequest._retry = true;
const refreshedHeader = await getHeader();
api.defaults.headers = refreshedHeader;
originalRequest.headers = refreshedHeader;
return Promise.resolve(api(originalRequest));
}
return Promise.reject(error);
}
);
// Call paginated API and return number of requests needed.
const getQueryCount = await api.get(url, axiosConfig).catch((error) => {
throw error;
});
const totalItems = parseInt(getQueryCount.data['#attributes'].count);
const queriesNeeded = Math.ceil(totalItems / 100);
// Loop through paginated API and push data to dataToReturn
const dataToReturn = [];
for (let i = 0; i < queriesNeeded; i++) {
setTimeout(async () => {
try {
const res = await api.get(`${url}?offset=${i * 100}`, axiosConfig);
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const { data } = res;
const arrayName = Object.keys(data)[1];
const selectedData = await data[arrayName];
selectedData.map((item) => {
dataToReturn.push(item);
});
if (i + 1 === queriesNeeded) {
console.log(dataToReturn);
return dataToReturn;
}
} catch (error) {
console.error(error);
}
}, 3000 * i);
}
};
The issue that I'm having is that getVendors() is returned before makeRequest() has finished getting the data.
Looks like your issue stems from your use of setTimeout. You're trying to return the data from inside the setTimeout call, and this won't work for a few reasons. So in this answer, I'll go over why I think it's not working as well as a potential solution for you.
setTimeout and the event loop
Take a look at this code snippet, what do you think will happen?
console.log('start')
setTimeout(() => console.log('timeout'), 1000)
console.log('end')
When you use setTimeout, the inner code is pulled out of the current event loop to run later. That's why end is logged before the timeout.
So when you use setTimeout to return the data, the function has already ended before the code inside the timeout even starts.
If you're new to the event loop, here's a really great talk: https://youtu.be/cCOL7MC4Pl0
returning inside setTimeout
However, there's another fundamental problem here. And it's that data returned inside of the setTimeout is the return value of the setTimeout function, not your parent function. Try running this, what do you think will happen?
const foo = () => {
setTimeout(() => {
return 'foo timeout'
}, 1000)
}
const bar = () => {
setTimeout(() => {
return 'bar timeout'
}, 1000)
return 'bar'
}
console.log(foo())
console.log(bar())
This is a result of a) the event loop mentioned above, and b) inside of the setTimeout, you're creating a new function with a new scope.
The solution
If you really need the setTimeout at the end, use a Promise. With a Promise, you can use the resolve parameter to resolve the outer promise from within the setTimeout.
const foo = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 1000)
})
}
const wrapper = async () => {
const returnedValue = await foo()
console.log(returnedValue)
}
wrapper()
Quick note
Since you're calling the setTimeout inside of an async function, you will likely want to move the setTimeout into it's own function. Otherwise, you are returning a nested promise.
// don't do this
const foo = async () => {
return new Promise((resolve) => resolve(true))
}
// because then the result is a promise
const result = await foo()
const trueResult = await result()

Execute function if and only if a certain amount of time has elapsed since subscription with RxJS

I am making an API call through an Observable. If this API call takes more than 200ms, I would like to show a loading screen (by assigning 'true' to my 'loading' variable), otherwise I don't want to show anything, in order to avoid a blink on screen.
Is there an RxJS operator capable of doing this ?
this.apiService.get(`/api/someEndpoint`)
// I hope for something like
.triggerIfAtLeastThisAmountOfTimeHasElapsed(200, () => {
this.loading = true;
})
.subscribe(response => {
// Process the response
this.loading = false;
});
There are many ways to do this so you can use for example this:
const api = this.apiService.get(`/api/someEndpoint`);
const loading = Observable
.timer(1000)
.do(() => loading = true) // show loading
.ignoreElements(); // or `filter(() => false)
Observable.merge(api, loading)
.take(1)
.subscribe(() => loading = false);
Along the same lines of Martin's response, this is an example that should simulate your context
const obs1 = Observable.timer(200).take(1);
const apiSubject = new Subject<string>();
const apiObs = apiSubject.asObservable();
const apiExecutionElapsed = 1000;
const obs3 = Observable.merge(obs1, apiObs);
let loading = undefined;
obs3.subscribe(
data => {
console.log(data);
if (loading === undefined && data === 0) {
loading = true;
} else {
loading = false;
}
console.log('loading', loading);
},
console.error,
() => {
loading = false;
console.log('loading', loading);
}
)
setTimeout(() => {
apiSubject.next('I am the result of the API');
apiSubject.complete()}, apiExecutionElapsed)
If the execution of the api (apiExecutionElapsed) takes longer than the configured timer (200 ms in this case) you see the loading flag to become first true and then false. Otherwise it remains always false.

Unsubscribe in rxjs

I'm new to rxjs.
what is the problem with this code that subscribe method not work correctly.
this.http.postReq(this.api, data)
.subscribe((value) => {
this.toastr.success(value, "Successfull");
this.isLoading = false;
}, (err) => {
this.toastr.error(err.error, "Error")
this.isLoading = false;
}).unsubscribe();
}
but when remove ".unsubscribe()" its work correctly.
an example that work correctly in this manner.
To do this :
let someSubscription = this.http.postReq(this.api, data)
.subscribe((value) => {
this.toastr.success(value, "Successfull");
this.isLoading = false;
someSubscription.unsubscribe();
}, (err) => {
this.toastr.error(err.error, "Error")
this.isLoading = false;
});
So now , your request is unsubscribed after getting the response (which you want) .

Resources