Promise for-loop with Ajax requests - ajax

I'm creating a native JavaScript application which does loads of Ajax calls in the same process at a certain time. Instead of going through a normal for loop and do them all at once, I thought I'd wait for the Ajax call to complete and then do the next one.
With the help of Stackoverflow I've managed to do this like the following:
function ajaxCall(index) {
return new Promise(function(resolve) {
// Ajax request {
resolve();
// }
});
}
Promise.resolve(0).then(function loop(i) {
if (i < length) {
return ajaxCall(i).thenReturn(i + 1).then(loop);
}
}).then(function() {
// for loop complete
});
Promise.prototype.thenReturn = function(value) {
return this.then(function() {
return value;
});
};
However, this is too slow for me. I want to be able to keep a var which keeps track of how many Ajax calls are currently in the process so that I can limit the amount.
I've tried multiple things, but I keep running into ininite loops or not getting to the desired result.
How can I do multiple, limited by a specific number, async Ajax calls using the Promise for loop?

Sounds like you want a version of Promise.all that takes an array of asynchronous functions rather than promises, and a ceiling on the number of simultaneous operations. Something like:
Promise.allFuncs = (funcs, n) => {
n = Math.min(n, funcs.length);
var results = [];
var doFunc = i => funcs[i]().then(result => {
results[i] = result; // store result at the correct offset
if (n < funcs.length) {
return doFunc(n++);
}
});
// start only n simultaneous chains
return Promise.all(funcs.slice(0, n).map((p, i) => doFunc(i)))
.then(() => results); // final result
};
// --- Example: ---
var log = msg => div.innerHTML += msg + "<br>";
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var example = ['a','b','c','d','e','f'].map(name =>
() => (log("started "+name),wait(2000).then(() => (log("ended "+ name), name))));
Promise.allFuncs(example, 2)
.then(results => log("Results: "+ results))
.catch(log);
<div id="div"></div>

Related

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

Dexie toArray() Promise

I need to create and consume an array from Dexie ordeBy Promise
var list = [];
const ms = wmsLocalDb.table1.orderBy("index").toArray();
ms.each(m => list.push(m)).then(
//When list is complete I want to consume
for (var i = 0; i < list.length; i++) {
//something
}
);
But i cannot read list array.
Best regards
Ingd
You want something along the following lines :
wmsLocalDb.table1.orderBy("index").toArray()
.then(list => {
list.forEach(item => {
//something
});
})
.catch(error => {
// handle error
});

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.

channel.on not recognized as a function in Twilio API

I have the following function in a ReactJS app that is supposed to initialize the Twilio services that I am using. However, it seems like the Twilio channels are not being accessed correctly. Here is my code:
componentDidMount() {
let chatClient = this;
$.ajax({
method: "GET",
url: 'get_twilio_token',
data: {device: chatClient.device},
success: (data) => {
let accessManager = new Twilio.AccessManager(data.token);
//let messagingClient = new Twilio.Chat.Client(data.token);
let messagingClient = new Twilio.Chat.Client.create(data.token).then(client => {
client.getUserChannelDescriptors().then(channels => {
let channelsHash = {};
console.log('inside callback of messagingClient2')
channels.items.map(channel => {
channel.on('messageAdded', () => {})
channelsHash[channel.uniqueName] = channel;
});
});
});
}
});
}
This function throws an error message saying TypeError: channel.on is not a function in the line channel.on('messageAdded', () => {}).
Any help is appreciated. Thanks.
getUserChannelDescriptors() returns ChannelDescriptors not Channels. To get Channel you have to call getChannel for the descriptor: https://media.twiliocdn.com/sdk/js/chat/releases/1.0.0/docs/ChannelDescriptor.html#getChannel__anchor
This is how I've done it
async function getChannels() {
// Initialize the chat client
this.chatClient = new Chat(this.state.twilioToken);
await this.chatClient.initialize();
// Get channel descriptors
this.chatClient.getUserChannelDescriptors().then(paginator => {
let channels = [];
let channelsBulkFetch = [];
if (paginator.items.length) {
channels = paginator.items;
// Loop through all channels and call getChannel() for each cahnnel
for (let i = 0; i < paginator.items.length; i++) {
channelsBulkFetch.push(channels[i].getChannel());
}
// Loop through each channel detailed object and perform various operations
channels.map(channel => {
// Do whatever you want with channel object
channel.on('messageAdded', this.messageAdded);
});
}
})
}
And for sorted channels with respective to last message timestamp
async function getSortedChannels() {
// Initialize the chat client
this.chatClient = new Chat(this.state.twilioToken);
await this.chatClient.initialize();
// Get channel descriptors
this.chatClient.getUserChannelDescriptors().then(paginator => {
let channels = [];
let sortedChannels = [];
let channelsBulkFetch = [];
if (paginator.items.length) {
channels = paginator.items;
// Loop through all channels and call getChannel() for each cahnnel
for (let i = 0; i < paginator.items.length; i++) {
channelsBulkFetch.push(channels[i].getChannel());
}
/**
* Additional part for sorting
*/
sortedChannels = channels.sort(function (a, b) {
// Turn strings into dates, and then subtract them
// If channel doesn't have any message consider the dateDreated for sorting
return new Date(b.lastMessage ? b.lastMessage.timestamp : b.dateCreated) - new Date(a.lastMessage ? a.lastMessage.timestamp : a.dateCreated);
});
// Loop through each channel detailed object and perform various operations
sortedChannels.map(channel => {
// Do whatever you want with channel object
channel.on('messageAdded', this.messageAdded);
});
}
})
}

Angular $http, $q: track progress

Is there a way to track progress of http requests with Angular $http and $q? I'm making $http calls from a list of urls and then using $q.all I'm returning result of all requests. I would like to track progress of each request (promise resolved) so that I can show some progress to the user. I'm thinking of emitting event when a promise gets resolved but I'm not sure where should that be.
var d = $q.defer();
var promises = [];
for(var i = 0; i < urls.length; i++){
var url = urls[i];
var p = $http.get(url, {responseType: "arraybuffer"});
promises.push(p);
}
$q.all(promises).then(function(result){
d.resolve(result);
}, function(rejection){
d.reject(rejection);
});
return d.promise;
EDIT:
OK, after a bit of fiddling, this is what I've come up with
var d = $q.defer();
var promises = [];
var completedCount = 0;
for(var i = 0; i < urls.length; i++){
var url = urls[i];
var p = $http.get(url, {responseType: "arraybuffer"}).then(function(respose){
completedCount = completedCount+1;
var progress = Math.round((completedCount/urls.length)*100);
$rootScope.$broadcast('download.completed', {progress: progress});
return respose;
}, function(error){
return error;
});
promises.push(p);
}
$q.all(promises).then(function(result){
d.resolve(result);
}, function(rejection){
d.reject(rejection);
});
return d.promise;
Not sure if it is the right way of doing it.
I see you have already edit your own code, but if you need a more overall solution, keep reading
I once made a progress solution based on all pending http request (showing a indicator that something is loading, kind of like youtube has on the top progress bar)
js:
app.controller("ProgressCtrl", function($http) {
this.loading = function() {
return !!$http.pendingRequests.length;
};
});
html:
<div id="fixedTopBar" ng-controller="ProgressCtrl as Progress">
<div id="loading" ng-if="Progress.loading()">
loading...
</div>
</div>
.
Hardcore
For my latest project it wasn't just enought with just request calls. I started to get into sockets, webworker, filesystem, filereader, dataChannel and any other asynchronous calls that use $q. So i start looking into how i could get all the pending promises (including $http). Turns out there wasn't any angular solution, so i kind of monkey patched the $q provider by decorating it.
app.config(function($provide) {
$provide.decorator("$q", function($delegate) {
// $delegate == original $q service
var orgDefer = $delegate.defer;
$delegate.pendingPromises = 0;
// overide defer method
$delegate.defer = function() {
$delegate.pendingPromises++; // increass
var defer = orgDefer();
// decreass no mather of success or faliur
defer.promise['finally'](function() {
$delegate.pendingPromises--;
});
return defer;
}
return $delegate
});
});
app.controller("ProgressCtrl", function($q) {
this.loading = function() {
return !!$q.pendingPromises;
};
});
This may not perhaps fit everyone needs for production but it could be useful to developers to see if there is any unresolved issues that has been left behind and never gets called
Make a small general helper function:
function allWithProgress(promises, progress) {
var total = promises.length;
var now = 0;
promises.forEach(function(p) {
p.then(function() {
now++;
progress(now / total);
});
})
return $q.all(promises);
}
Then use it:
var promises = urls.map(function(url) {
return $http.get(url, {responseType: "arraybuffer"});
});
allWithProgress(promises, function(progress) {
progress = Math.round(progress * 100);
$rootScope.$broadcast('download.completed', {progress: progress});
}).catch(function(error) {
console.log(error);
});

Resources