Resolve promise after callback it's executed - javascript

I have the following code on which I am trying to block the execution of the method _saveAddress multiple time, so I made a promise for this method.
const [pressEventDisabled, setPressEventDisabled] = useState(false);
<TouchableOpacity style={style.button_container} activeOpacity={1} disabled={pressEventDisabled} onPress={async () => {setPressEventDisabled(true); await _saveAddress(); setPressEventDisabled(false);}} >
The problem is that I want to resolve the promise after the callback method it's executed. It's there any way to wait for the dispatch function to execute or to resolve the promise inside the callback method?
This is the method for saving the address:
const _saveAddress = () => new Promise(async (resolve) => {
var valid = _validate();
if (valid) {
const address = createAddressJson();
if (addressId) {
var addressIdProperty = {
id: addressId
};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, _onAddressSaveEditCallback));
} else {
dispatch(addAddress(address, _onAddressSaveEditCallback));
}
} else {
//notify
notifyMessage(strings.fill_required_inputs_validation);
resolve();
}
});
This is the callback method:
const _onAddressSaveEditCallback = async (success: boolean, apiValidations: any, address ? : Address, ) => {
if (success) {
if (typeof callback == 'function') {
callback(address);
}
await Navigation.pop(componentId);
} else {
setDataValidations(apiValidations);
}
};

Just do exactly what you say in the title. Nothing more, nothing less:
if (addressId) {
var addressIdProperty = {id: addressId};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
} else {
dispatch(addAddress(address, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
}
Of course, since you are passing async () => {} to addAddress instead of _onAddressSaveEditCallback you have to call _onAddressSaveEditCallback yourself since addAddress will be calling the async () => ...
But mixing promises and callbacks like this isn't great. It leads to weird looking and sometimes confusing code. A better solution is to promisify addAddress:
function addAddressPromise (address) {
return new Promise((resolve, reject) => {
addAddress(address, (success, validations, address) {
if (success) return resolve(address);
else reject(validations)
});
});
}
Now you can wait for addAddress:
const _saveAddress = async () => {
// Don't create new promise here, we do it in addAddress..
// ...
let result = await addAddressPromise(address);
dispatch(result);
await _onAddressSaveEditCallback();
// ...
}

Related

async function is not continuing to next line

I'm attempting to setup an async function so that my next step will not start until the function finishes.
I coded one module to connect to mongodb server, and then check to see if it's connected. These two functions work well together.
const mongoose = require('mongoose');
const mongoServer = `mongodb://127.0.0.1/my_database`;
const consoleColor = { green: '\x1b[42m%s\x1b[0m', yellow: '\x1b[43m%s\x1b[0m', red: '\x1b[41m%s\x1b[0m' }
exports.connectMongoose = () => {
mongoose.connect(mongoServer, { useNewUrlParser: true });
}
exports.checkState = () => {
const mongooseState = mongoose.STATES[mongoose.connection.readyState];
return new Promise((resolve) => {
if (mongooseState === 'connected') {
console.log(consoleColor.green, `Mongoose is ${mongooseState}.`);
resolve();
} else if (mongooseState === 'connecting') {
console.log(`Mongoose is ${mongooseState}.`);
setTimeout(() => {
this.checkState();
}, 1000);
} else {
console.log(consoleColor.red, `Mongoose is ${mongooseState}.`);
}
});
}
The next thing I tried to do was connect to the mongo db using my connectMongoose function, and then call a second function that will run my checkState function, and only perform the next function if it resolves (the if statement for the "connected" state.
const dbconfig = require('./dbconfig')
dbconfig.connectMongoose()
const testAwait = async () => {
await dbconfig.checkState();
console.log("Do this next");
}
testAwait()
The testAwait function runs, but it does not get to the console.log function which leads me to believe I'm doing something wrong when passing the resolve.
setTimeout(() => {
this.checkState();
}, 1000);
When this block is hit, the promise is never resolved. The original promise needs to resolve (as your code is currently, if the status is connecting, a new promise is created, but nothing waits for it, and the original promise never resolves). You could go with a pattern like this:
let attempts = 0;
const isConnected = async () => {
console.log("checking connection state...");
attempts++;
if (attempts >= 5) {
return true;
}
return false;
}
const wait = ms => new Promise(res => setTimeout(res, ms));
const checkState = async () => {
while (!(await isConnected())) {
await wait(1000);
}
return;
};
checkState().then(() => console.log("done"));
But to keep it more in line with what you've written, you could do:
const checkState = () => {
const mongooseState = Math.random() > 0.2 ? "connecting" : "connected";
return new Promise((resolve) => {
if (mongooseState === 'connected') {
console.log(`Mongoose is ${mongooseState}.`);
resolve();
} else if (mongooseState === 'connecting') {
console.log(`Mongoose is ${mongooseState}.`);
setTimeout(() => {
checkState().then(resolve);
}, 1000);
}
});
}
checkState().then(() => console.log("done"));
I think the issue here in the above code, you are only resolving your promise once. There is no rejection either. Thus, your code is blocked inside the promise. See the below example. You should exit the promise in any case resolve or reject.
const random = parseInt(Math.random());
const testAwait =
async() => {
await new Promise((resolve, reject) => {
if (random === 0) {
resolve(random);
} else {
reject(random);
}
});
console.log("Do this next");
}
testAwait()

jest Mocking a jQuery function from a promise

I have a function that calls a jQuery function.
the jQuery function called dataFunc and should return an object.
I want to test the promise, not the dataFunc function.
For that, I want to mock the response that dataFunc should return
I want this row const { data } = await service.auth( buttonData ); data to return
{ access_level: 0 };
How can I do that?
This is my code:
This function with the promise I want to test:
auth(buttonData){
const myPromise = new Promise((resolve, reject) => {
const success = (e, data) => {
resolve({data, error: null});
};
const error = () => {
resolve({data: null, error: 'Error'});
};
jQuery(buttonData).dataFunc({
success,
error,
});
});
return myPromise;
}
This is what I have done in jest so far:
describe('service.test.js', () => {
beforeEach(() => {
global.jQuery = () => {
return {
dataFunc: jest.fn(() => ({access_level: 0})),
};
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should do something', async () => {
// Arrange
const service = new Service();
const approveButton = document.createElement('button');
// Act
const {data} = await service.auth(buttonData);
console.log(data);
});
});
To fulfill auth function you need either reject or resolve the value.
However, when you mock jQuery method dataFunc to return an explicit value, you override the default behavior and it never calls resolve or reject. Therefore your promise will hang.
You don't necessarily need to mock but provide the original functionality dataFunc carries or provide one that is necessary for the current test.
To fix your example you can pass the argument and call it.
global.jQuery = () => {
return {
dataFunc: ({success, error}) => {
success(jest.fn(), {access_level: 0})
},
};
};

Repeat async function until true

I have an async function that checks for the status of an order (checkOrderStatus()). I would like to repeat this function until it returns either "FILLED" or "CANCELED", then use this return value in another function to decide to continue or stop the code. Every order goes through different status before being "FILLED" or "CANCELED", therefore the need to repeat the checkOrderStatus() function (it is an API call).
What I have now is this, to repeat the checkOrderStatus() function:
const watch = filter => {
return new Promise(callback => {
const interval = setInterval(async () => {
if (!(await filter())) return;
clearInterval(interval);
callback();
}, 1000);
});
};
const watchFill = (asset, orderId) => {
return watch(async () => {
const { status } = await checkOrderStatus(asset, orderId);
console.log(`Order status: ${status}`);
if (status === 'CANCELED') return false;
return status === 'FILLED';
});
};
I then call watchFill() from another function, where I would like to check its return value (true or false) and continue the code if true or stop it if false:
const sellOrder = async (asset, orderId) => {
try {
const orderIsFilled = await watchFill(asset, orderId);
if (orderIsFilled) {
//… Continue the code (status === 'FILLED'), calling other async functions …
}
else {
//… Stop the code
return false;
}
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
However, this does not work. I can see the status being updated in the terminal via the console.log in watchFill(), but it never stops and most importantly, the value in the orderIsFilled variable in sellOrder() does not get updated, whatever the value returned by watchFill() becomes.
How can I achieve the desired behavior?
watch never calls resolve (in the original code, this is misleadingly named callback()) with any value, so there's no way const orderIsFilled = await watchFill(asset, orderId); will populate orderIsFilled with anything but undefined.
If you save the result of await filter() in a variable and pass it to
callback as callback(result), your code seems like it should work.
That said, the code can be simplified by using a loop and writing a simple wait function. This way, you can return a value (more natural than figuring out how/when to call resolve), keep the new Promise pattern away from the logic and avoid dealing with setInterval and the bookkeeping that goes with that.
const wait = ms =>
new Promise(resolve => setTimeout(resolve, ms))
;
const watch = async (predicate, ms) => {
for (;; await wait(ms)) {
const result = await predicate();
if (result) {
return result;
}
}
};
/* mock the API for demonstration purposes */
const checkOrderStatus = (() => {
let calls = 0;
return async () => ({
status: ++calls === 3 ? "FILLED" : false
});
})();
const watchFill = (asset, orderId) =>
watch(async () => {
const {status} = await checkOrderStatus();
console.log(`Order status: ${status}`);
return status === "CANCELLED" ? false : status === "FILLED";
}, 1000)
;
const sellOrder = async () => {
try {
const orderIsFilled = await watchFill();
console.log("orderIsFilled:", orderIsFilled);
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
sellOrder();
You can use recursive functionality like this:
const checkOrderStatus = async () => {
// ... function does some work ...
await someOtherFunction() // you can use here the other async function as well
// ... function does some more work after returning from await ...
if(/* if status is FILLED or CANCELED */) {
// return true or false or some info about response for your needs
} else {
checkOrderStatus();
}
}
// this will response back when status will be FILLED or CANCELED
await checkOrderStatus();
The watch function clears the interval timer after the first call if filter resolves with false. setInterval doesn't wait for an async function to finish executing either so you'll have to create a loop yourself. Try this:
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
const watch = async check => {
while (true) {
if (await check()) {
return;
}
await delay(1000);
}
};
Because watch only resolves when check succeeds, it is not possible to fail so you don't need to check for it (this might be a bug in your code):
const sellOrder = async (asset, orderId) => {
try {
await watchFill(asset, orderId);
//… Continue the code (status === 'FILLED'), calling other async functions …
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
p-wait-for contains an excellent implementation of this. You can use it like so:
import pWaitFor from 'p-wait-for';
const watchFill = (asset, orderId) => pWaitFor(async () => {
const { status } = await checkOrderStatus(asset, orderId);
console.log(`Order status: ${status}`);
if (status === 'CANCELED') return false;
return status === 'FILLED';
}, {
interval: 1000,
leadingCheck: false
});

Problem with .push with Asynchronous function

The Problem is with the uplines.push.
I always get an empty uplines array so the last part of the code doesn't run. The promises resolve later and I get the correct data. May I know how to go about doing it the correct way?
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
uplines = uplines.filter(
(v, i) => uplines.findIndex((index) => index === v) === i
); //remove duplicates
uplines.forEach((user) => {
if (user.chatId) {
sendTelegramMessage(user.chatId, saleToDisplay, currentUser.displayName);
console.log("Telegram Message Sent to " + user.displayName);
} else {
console.log(user.displayName + " has no chatId");
}
});
There are a few things that you have missed out while implementing the async call, which are explained in the inline comments in the code snippet.
A short explanation for what happened in your code is that in the line sale.rens.forEach you are passing an async function in the argument, which does not make any difference to the function forEach, it will execute it without waiting for it to complete.
Therefore in my answer I am using Promise.all to wait for all the async function calls to complete before returning the result.
// This is wrapped in an immediately executed async function because await in root is not supported here
(async () => {
const mockGetData = () => new Promise(resolve => setTimeout(resolve, 1000));
const sale = {
rens: [
{ userFid: 1 },
{ userFid: 2 },
{ userFid: 3 }
]
};
const getAllUplines = async () => {
const uplines = [];
const findUser = async (userFid) => {
// Simulating an async function call
const userDoc = await mockGetData();
console.log("User data received");
uplines.push(`User ${userFid}`);
};
const promises = [];
sale.rens.forEach(ren => { // This function in foreach does not have to be declared as async
// The function findUser is an async function, which returns a promise, so we have to keep track of all the promises returned to be used later
promises.push(findUser(ren.userFid));
});
await Promise.all(promises);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
})();
In order to get the results of getAllUplines() properly, you need to add await to all async functions called in getAllUplines().
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
await findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
await findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};

There is a function with a promise. Inside this function I call this function again (recursion). How to wait till recursed promise is resolved?

There are 2 functions that I need to run one-by-one: getUserPlaylists (receives Playlists) and getPlaylistTracks (receives Tracks for provided Playlist).
One response can have up to 50 tracks, so I need to use PageToken if I want to get the rest tracks. The problem is that I can not make a recursive function getPlaylistTracks to wait till the recursion is done.
function getPlaylistsWithTracks () {
return new Promise((resolve, reject) => {
getUserPlaylists()
.then(function (playlists) {
playlists.forEach(
async function (playlistObj) {
await getPlaylistTracks(playlistObj).then(function (tracks) {
playlistObj['tracks'] = tracks
})
})
console.log('resolve')
resolve(playlists)
})
})
}
function getPlaylistTracks (playlistObj, pageToken) {
return new Promise((resolve, reject) => {
let playlistTracks = []
let requestOptions = {
'playlistId': playlistObj['youtubePlaylistId'],
'maxResults': '50',
'part': 'snippet'
}
if (pageToken) {
console.log('pageToken:', pageToken)
requestOptions.pageToken = pageToken
}
let request = gapi.client.youtube.playlistItems.list(requestOptions)
request.execute(function (response) {
response['items'].forEach(function (responceObj) {
let youtubeTrackTitle = responceObj.snippet.title
if (youtubeTrackTitle !== 'Deleted video') {
let youtubeTrackId = responceObj.snippet.resourceId.videoId
playlistTracks.push({
youtubePlaylistId: playlistObj.playlistId,
youtubePlaylistTitle: playlistObj.playlistTitle,
youtubeTrackId: youtubeTrackId,
youtubeTrackTitle: youtubeTrackTitle,
})
}
})
// Here I need to wait a bit
if (response.result['nextPageToken']) {
getPlaylistTracks(playlistObj, response.result['nextPageToken'])
.then(function (nextPageTracks) {
playlistTracks = playlistTracks.concat(nextPageTracks)
})
}
})
resolve(playlistTracks)
})
}
getPlaylistsWithTracks()
In my case in console I see the next:
> resolve
> pageToken: 123
> pageToken: 345
but, I want to see resolve the last.
How to wait till recursion is executed?
Avoid the Promise constructor antipattern, and (don't) use forEach with async functions properly.
Furthermore, there is nothing special about recursion. It's like any other promise-returning function call that you would want to wait for - put it in your then chain or await it. (The latter is considerably easier).
async function getPlaylistsWithTracks() {
const playlists = await getUserPlaylists();
for (const playlistObj of playlists) {
const tracks = await getPlaylistTracks(playlistObj);
playlistObj.tracks = tracks;
}
console.log('resolve')
return playlists;
}
async function getPlaylistTracks(playlistObj, pageToken) {
let playlistTracks = []
let requestOptions = {
'playlistId': playlistObj['youtubePlaylistId'],
'maxResults': '50',
'part': 'snippet'
}
if (pageToken) {
console.log('pageToken:', pageToken)
requestOptions.pageToken = pageToken
}
let request = gapi.client.youtube.playlistItems.list(requestOptions)
const response = await new Promise((resolve, reject) => {
request.execute(resolve); // are you sure this doesn't error?
});
response['items'].forEach(function (responceObj) {
let youtubeTrackTitle = responceObj.snippet.title
if (youtubeTrackTitle !== 'Deleted video') {
let youtubeTrackId = responceObj.snippet.resourceId.videoId
playlistTracks.push({
youtubePlaylistId: playlistObj.playlistId,
youtubePlaylistTitle: playlistObj.playlistTitle,
youtubeTrackId: youtubeTrackId,
youtubeTrackTitle: youtubeTrackTitle,
})
}
})
if (response.result['nextPageToken']) {
const nextPageTracks = await getPlaylistTracks(playlistObj, response.result['nextPageToken']);
playlistTracks = playlistTracks.concat(nextPageTracks);
}
return playlistTracks;
}

Categories

Resources