promises with search function - javascript

I have an array of objects, there is a function to search for a book by name, I want to use the search function through promises, after a successful search, the convert function should be executed. when I do this, I get an empty array
let book1 = {
name: "wind",
author: 'smith',
size: 314,
};
let book2 = {
name: "moon",
author: 'wild',
size: 421,
};
let book3 = {
name: "sun",
author: 'jacob',
size: 510,
};
let books1 = [book1, book2, book3];
let books = [];
function searchName(search) {
return new Promise(function(resolve, reject) {
books1 = books1.filter(function(elem) {
elem.name.includes(search)
})
})
};
function convert() {
return books = books1.map(item => item.name);
};
searchName("wind").then(convert);
console.log(books);

Add return statement in book1.filter to get filtered array
Call resolve callback in Promise body
If you do not catch rejected Promise you can omit reject callback in Promise executor's arguments
console.log(books) at the end of your example will always return an empty array because Promise will be executed after the console.log. Read about Microtasks, here is a good explanation https://javascript.info/microtask-queue
Try this code:
function searchName(search) {
return new Promise(function (resolve) {
books1 = books1.filter(function (elem) {
return elem.name.includes(search);
});
if (books1.length) {
resolve();
}
});
}
function convert() {
books = books1.map(item => item.name);
console.log(books);
return books;
}
searchName('wind').then(convert);

You're using Promises incorrectly in a variety of ways -
const books = // use const for bindings that will not be reassigned
[ { name: "wind" // objects can be declared in an array, if you wish
, author: 'smith' // <-- book1
, size: 314,
}
, { name: "moon" // <-- book2
, author: 'wild'
, size: 421
}
, { name: "sun" // <-- book3
, author: 'jacob'
, size: 510
}
]
const searchBooks = (query = "") =>
new Promise((resolve, _) => // <-- reject unused
resolve(books.filter(b => // call resolve, don't reassign books
b.name.includes(query)
))
)
const convert = (books = []) => // receives argument
books.map(b => b.name) // return a value, don't use reassignment
const input =
document.querySelector("input")
input.addEventListener
( "keyup"
, e =>
searchBooks(e.target.value) // gets filtered books
.then(convert) // passes books to convert, gets result
.then(console.log, console.error) // then log the result, or error
)
<input placeholder="search here..."/>
These are all common mistakes beginners make when first using Promises. I think this related Q&A will be very helpful to you.
In this example, we wrap a Promise around filter but it is purely synchronous and actually unnecessary -
const searchBooks = (query = "") =>
books.filter(b =>
b.name.includes(query)
)
In a real scenario, maybe the data is coming from over the network. Then we'd have use for Promises -
const fetchJson = (url = "") =>
fetch(url).then(res => res.json())
const searchBooks = (query = "") =>
fetchJson("./path/to/books.json") // fetch the books data
.then(books => // then using the books
books.filter(b => // filter the books
b.name.includes(query)
)
) // .then returns another promise
Related, async/await make it easy to write asynchronous code that appears more like synchronous code. Using async our function will automatically return a Promise, and it allows us to await any expression inside the function's body -
const fetchJson = (url = "") =>
fetch(url).then(res => res.json())
const searchBooks = async (query = "") => // <-- async keyword
{ const books =
await fetchJson("./path/to/books.json") // <-- await keyword
return books.filter(b =>
b.name.includes(query)
)
}

Related

How to wait for the final result of a for loop with API calls?

This loop is going to run an arbitrary amount of times, and I want to get the result out of it after them all. Anything I try (promisifying, async/await, nesting functions, et al) seems to be a dead end. I don't get why I cannot just stick a .then on the API call, or on the function I made here. But I suspect the problem is more fundamental with my understanding, because I can't seem to even get just the "data" to return...same inability to wait on the API call. Wrapping it in a promise loses the "data" and pulling it with the "for loop" inside there doesn't work either. This is making me question my entire progress with JS/implementing other people's APIs.
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
//return data //doesnt return "data" from gotPeeps?
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}}}})}
gotPeeps()
EDIT
To the comments:
I'm trying to get the results after the for loop is complete.
The "loop" I was referring to is the "for of" over the data Object.
"Putting the code after the loop but inside the callback" does not work. I have a previous question in my week of failing to solve this:
How to solve race condition in Javascript?
Here is the whole thing, with some past versions commented out.:
const tournamentModel = require('../models/tournamentSchema')
require('dotenv').config()
const challonge = require('challonge');
module.exports = {
name: 'getmatches',
aliases: ['gm'],
cooldown: 0,
description: 'Get Challonge data into console.',
execute(message, args, cmd, client, Discord, profileData) {
let peep = ''
let peepsList = ''
const tournamentURL = 'TESTING_Just_Sign_Up_If_You_See_This850587786533011496'
const challongeClient = challonge.createClient({
apiKey: process.env.CHALLONGE_API_KEY,
})
const getPlayer = (playerXId) => {
return new Promise((resolve, reject) => {
challongeClient.participants.show({
id: tournamentURL,
participantId: playerXId,
callback: (err, data) => {
if (err) {
reject(err);
return;
}
peep = data.participant.misc
peepsList.push(peep)
console.log(peepsList)
console.log('RUNNING GET PLAYER', playerXId, playerIndexCount)
resolve(peepsList);
}
});
});
}
const peepsList = []
const matchList = []
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
include_participants: 1,
include_matches: 1,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}
}
}
/*// GET PLAYERS
getPlayer(value.match.player1Id)
.then(() => {
getPlayer(value.match.player2Id)
})
}
*/
}
)
}
gotPeeps()
}}
You can make this function return a promise and await the function (as long as your function is an async function)
const gotPeeps = () => {
return new Promise((resolve, reject) => {
const peepsList = []; // declare the empty array to e filled
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === "open") {
peepsList.push(value.match.player1Id, value.match.player2Id);
}
}
resolve(peepsList); // resolve the promise with the filled array
// TODO: handle reject
},
});
})
};
(async () => {
try {
const result = await gotPeeps();
} catch (error) {
// TODO: handle error
}
})();

How to access a property of an object which is returned from a function using javascript?

i have a method which returns an object. How do i retrieve the property of that object
below is my code,
const called_method = async (context, user) => {
const available = await context.dbHandler({
sql:
'SELECT SUM(first) AS first, SUM(second) AS second FROM items;',
values: { id: user[0].comp_id },
});
return available[0];
};
const some_method = () => {
const available = called_method(context, user);
const data = {
first: available.first, //here i get error says first doesnt exist on type promise
second: available.second, //here i get error says first doesnt exist on type promise
}
}
How can i return first and second from the called method and access it in some_method.
could soemone help me with this. thanks.
Async functions always return a promise, therefore if you want to get its value you have to wait until it settles. You can set some_method as an asynchronous function as well:
const some_method = async () => {
const available = await called_method(context, user); // wait until the promise settles
const data = {
first: available.first,
second: available.second
}
}
To interact with the eventual value of a promise, you either need to use it's .then method:
const some_method = () => {
called_method(context, user)
.then(available => {
const data = {
first: available.first,
second: available.second,
}
});
}
Or you need to put your code in an async function and await the promise
const some_method = async () => {
const available = await called_method(context, user);
const data = {
first: available.first,
second: available.second,
}
}
You have to wait for the async function:
const called_method = async (context, user) => {
const available = await context.dbHandler({
sql:
'SELECT SUM(first) AS first, SUM(second) AS second FROM items;',
values: { id: user[0].comp_id },
});
return available[0];
};
const some_method = () => {
called_method(context, user).then(function(available) {
const data = {
first: available.first, //here i get error says first doesnt exist on type promise
second: available.second, //here i get error says first doesnt exist on type promise
}
}
}
Or you can also make your some method async and await for called_method:
const called_method = async (context, user) => {
const available = await context.dbHandler({
sql:
'SELECT SUM(first) AS first, SUM(second) AS second FROM items;',
values: { id: user[0].comp_id },
});
return available[0];
};
const some_method = async () => {
const available = await called_method(context, user);
const data = {
first: available.first, //here i get error says first doesnt exist on type promise
second: available.second, //here i get error says first doesnt exist on type promise
}
}

Defer an async service call until promise is resolved

I want to create an array of Twilio API calls to be resolved later in Promise.all.
TwilioClient.messages.create(payload) is getting called immediately inside the returned promise (A). How can I defer until the Promise.all chain resolves it (B)?
let twilioTexts = [];
if (twilioNumbers) {
twilioTexts = twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber));
// (A) ==> Twilio is sending messages here upon assigning to variable `twilioTexts`.
}
await Promise.all([
// do other stuff
...twilioTexts, // (B) ==> instead, Twilio should make all calls here
]);
...
function getTwilioText(mailTokens, phoneNumber) {
return new Promise((resolve, reject) => {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
TwilioClient.messages.create(payload);
});
}
Edit:
await Promise.all([
// do other async stuff in parallel
// check if twilioNumbers then spread map into Promise.all array
twilioNumbers && ...twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber)),
]);
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
Your second example is almost there, but Promise.all works on an array of promises. The twilioNumbers &&... line produces an array of promises, so you've got an array of promises inside another array, which won't work.
So you can use the spread operator:
await Promise.all([
// do other stuff
...(twilioNumbers
? twilioNumbers.map(phoneNumber => getTwilioText(mailTokens, phoneNumber))
: []
),
]);
...
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
At this point, it's a bit verbose, so you might want to factor that part out:
await Promise.all([
// do other stuff
getTwilioTexts(twilioNumbers, mailTokens),
]);
...
// returns a promise for an array
async function getTwilioTexts(numbers, mailTokens) {
return numbers
? Promise.all(numbers.map(num => getTwilioText(mailTokens, num)))
: [];
}
function getTwilioText(mailTokens, phoneNumber) {
let payload = {
body: `[Incoming Order!]`,
from: 'my number',
to: phoneNumber,
};
return TwilioClient.messages.create(payload);
}
You seem to be looking for
const otherPromises = // do other stuff, *triggered first*
const twilioTexts = (twilioNumbers || []).map(phoneNumber => getTwilioText(mailTokens, phoneNumber));
await Promise.all([
...otherPromises,
...twilioTexts,
]);
Ultimately, it shouldn't matter though as you want to run the other stuff concurrently with sending texts anyway.

How to make run nested asynchronous methods synchronously?

How do I wrap this routine inside a Promise so that I only resolve when I get all the data?
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
});
})
});
EDIT: Any issues if I do it like this?
function getAccountsAllAtOnce() {
var accounts = [];
var required = 0;
var done = 0;
getAccounts(userId, accs => {
required = accs.length;
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
done = done + 1;
});
})
});
while(done < required) {
// wait
}
return accounts;
}
Let's put this routine into a separate function, so it is easier to re-use it later. This function should return a promise, which will be resolved with array of accounts (also I'll modify your code as small as possible):
function getAccountsWithTx(userId) {
return new Promise((resolve, reject) => {
var accounts = [];
getAccounts(userId, accs => {
accs.forEach(acc => {
getAccountTx(acc.id, tx => {
accounts.push({
'id': acc.id,
'tx': tx
});
// resolve after we fetched all accounts
if (accs.length === accounts.length) {
resolve(accounts);
}
});
});
});
});
}
The single difference is just returning a promise and resolving after all accounts were fetched. However, callbacks tend your codebase to have this "callback hell" style, when you have a lot of nested callbacks, and it makes it hard to reason about it. You can workaround it using good discipline, but you can simplify it greatly switching to returning promises from all async functions. For example your func will look like the following:
function getAccountsWithTx(userId) {
getAccounts(userId)
.then(accs => {
const transformTx = acc => getAccountTx(acc.id)
.then(tx => ({ tx, id: acc.id }));
return Promise.all(accs.map(transformTx));
});
}
Both of them are absolutely equivalent, and there are plently of libraries to "promisify" your current callback-style functions (for example, bluebird or even native Node util.promisify). Also, with new async/await syntax it becomes even easier, because it allows to think in sync flow:
async function getAccountsWithTx(userId) {
const accs = await getUserAccounts(userId);
const transformTx = async (acc) => {
const tx = getAccountTx(acc.id);
return { tx, id: acc.id };
};
return Promise.all(accs.map(transformTx));
}
As you can see, we eliminate any nesting! It makes reasoning about code much easier, because you can read code as it will be actually executed. However, all these three options are equivalent, so it is up to you, what makes the most sense in your project and environment.
I'd split every step into its own function, and return a promise or promise array from each one. For example, getAccounts becomes:
function getAccountsAndReturnPromise(userId) {
return new Promise((resolve, reject) => {
getAccounts(userId, accounts => {
return resolve(accounts);
});
});
};
And getAccountTx resolves to an array of { id, tx } objects:
function getAccountTransactionsAndReturnPromise(accountId) {
return new Promise((resolve, reject) => {
getAccountTx(account.id, (transactions) => {
var accountWithTransactions = {
id: account.id,
transactions
};
return resolve(accountWithTransactions);
});
});
};
Then you can use Promise.all() and map() to resolve the last step to an array of values in the format you desire:
function getDataForUser(userId) {
return getAccountsAndReturnPromise(userId)
.then(accounts=>{
var accountTransactionPromises = accounts.map(account =>
getAccountTransactionsAndReturnPromise(account.id)
);
return Promise.all(accountTransactionPromises);
})
.then(allAccountsWithTransactions => {
return allAccountsWithTransactions.map(account =>{
return {
id: account.id,
tx: tx
}
});
});
}

First finish reading data from Firebase and then do something else

So my problem is that empty array is returned because reading data from Firebase is not finished yet. I am using then method but it still executes everything inside then before.
var usersList = [];
const ref = firebase.database().ref()
ref.child('users').once('value').then(snap => {
snap.forEach(childSnap => {
const key = childSnap.key
ref.child(`users/${key}/points`).on('value', function(snapper) {
var points = snapper.val()
usersList.push({uid: key, points: points})
})
})
}).then(function() {
console.log(usersList)
})
Using then() itself is not a magical solution. You need to return a promise from within the callback, which your code is not doing. In this case you only want the final then() to be invoked once all the users are loaded, so you need to return a promise that only resolves when done.
const ref = firebase.database().ref()
ref.child('users').once('value').then(snap => {
var promises = [];
snap.forEach(childSnap => {
const key = childSnap.key
promises.push(
ref.child(`users/${key}/points`).once('value')
);
});
return Promise.all(promises);
}).then(function(snapshots) {
return snapshots.map(snapper => {
var points = snapper.val()
return {uid: key, points: points};
})
}).then(function(usersList) {
console.log(usersList)
})

Categories