How to use Knexjs synchronously on Apollo GraphQL Server - apollo-server

How can I use Knexjs to fetch data synchronously on a resolver in Apollo GraphQL Server? For instance, if I run the following query:
const resolvers = {
Query: {
MySchema: (_, args, { dataSources }, info) => {
var result = db.knex.select('*')
.from('SomeTable')
.where({SomeColumn:'SomeValue'})
.then(function(rows) {console.log(rows)})
.catch(function(error) {console.error(error)});
// Do something with the result here..
console.log(result);
return db.knex.select('*').from('SomeOtherTable')
}
}
}
The line console.log(result); just displays a Promise {<pending>} and by the time the line .then(function(rows) {console.log(rows)}) is executed (asynchronously), the main function will be already finished.
Is there a way to get the result of the database query instead of a Promise on line console.log(result);?

If you want your request to run synchronously and wait the result you need to add async/await
MySchema: async (_, args, { dataSources }, info) => {
var result = await db.knex.select('*')
.from('SomeTable')
.where({SomeColumn:'SomeValue'})
// result is now a value and not a promise
console.log(result)
}
As you noticed, I removed the .then and the .catch that are not relevant anylonger.
I highly recommend adding a try/catch around your awaited promise to avoid uncaught error.
MySchema: async (_, args, { dataSources }, info) => {
try {
var result = await db.knex.select('*')
.from('SomeTable')
.where({SomeColumn:'SomeValue'})
// result is now a value and not a promise
console.log(result)
} catch (e) {
// handle e
}
}
By the way, the return db.knex.select('*').from('SomeOtherTable') would benefit an await aswell to return data instead of a promise.

Related

How can I excute code after the entire request for GraphQL has finished in NestJS?

I'm trying to set up Sentry transactions with something like this:
(A globally registered interceptor)
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const transaction = Sentry.startTransaction({
op: 'gql',
name: 'GraphQLTransaction'
});
this.setTransaction(context, transaction); // adds a `transaction` property to the context
return next.handle().pipe(
tap((...args) => {
transaction.finish();
}),
);
}
and then inside a FieldMiddleware I track spans with something like this:
(A globally registered field middleware)
export const checkRoleMiddleware: FieldMiddleware = async (
ctx: MiddlewareContext,
next: NextFn,
) => {
try {
const { info, context: gqlCtx } = ctx;
const transaction: Transaction = gqlCtx.transaction;
const span = transaction.startChild({
op: 'resolver',
description: `${info.parentType.name}.${info.fieldName}`,
});
const result = await next();
span.finish();
return result;
} catch (e) {
// log error to console, since for some reason errors are silenced in field middlewares
console.error(e);
Sentry.captureException(e);
return next();
}
};
However, it seems that transaction.finished() inside the tap() operator gets called before fields are resolved.
Is there another operator that I should be using?

How to retry fetch in a loop when it throws an error

I have the following JS function:
func() {
return fetch({
...
}).then({
...
})catch({
...
})
}
In it I return a promise returned by fetch(). In the event that it fails (ie calls catch() block) I want to repeat the whole thing. Something like having the whole thing in a while (true) loop, but I can't figure out how to do this with promises.
Any suggestions?
you should have a close look to promises and async await.
async function fetchUntilSucceeded() {
let success = false;
while(!success) {
try {
let result = await fetch(...);
success = true;
//do your stuff with your result here
} catch {
//do your catch stuff here
}
}
}
If you just need the results:
async function fetchUntilSucceeded() {
while(true) {
try {
return await fetch(...);
}
}
}
But be careful with such code as it might never resolve! also it can send a lot of requests without any waittime in between.
You can simply write a loop and count down the attempts until one succeeds or you run out. async/await makes this easy. See below for a minimal, complete example.
Note that the fetch API uses the response.ok flag to ensure that the response status falls in the 200 range. Wrapping with a try/catch is only sufficient to cover connection failures. If the response indicates a bad request, a retry is likely inappropriate. This code resolves the promise in such cases but you could consider !response.ok as an error and retry if you wish.
const fetchWithRetry = async (url, opts, tries=2) => {
const errs = [];
for (let i = 0; i < tries; i++) {
// log for illustration
console.log(`trying GET '${url}' [${i + 1} of ${tries}]`);
try {
return await fetch(url, opts);
}
catch (err) {
errs.push(err);
}
}
throw errs;
};
fetchWithRetry("https://httpstat.us/400")
.then(response => console.log("response is OK? " + response.ok))
.catch(err => console.error(err));
fetchWithRetry("foo")
.catch(err => console.error(err.map(e => e.toString())));
fetchWithRetry("https://httpstat.us/200")
.then(response => response.text())
.then(data => console.log(data))
.catch(err => console.error(err));
Pass the tries parameter as -1 if you want an infinite number of retries (but this doesn't seem like the common case to me).

Jest not awaiting timeout before test completes

I have a test that awaits an async function, then awaits a timeout, then awaits another async function.
it('runs a snipe successfully', async () => {
const exitCode = await john.chat.sendMoneyInChat(channel.topicName, channel.name, "0.01", botUsername);
console.log('timeout?')
await timeout(3000);
console.log('timeout!');
console.log('running bal check')
let values;
const nbl = await croupier.checkWalletBalance(process.env.CROUPIER_RINGO_USERNAME);
expect(nbl).toEqual(123);
})
Based on my console.log output, the afterAll teardown process begins right after the timeout? log statement. In other words, I don't see "timeout!" in the console log – I see the console.log statements within the afterAll teardown.
What gives?
EDIT:
Thanks to #Metalmi's help, I've fixed one bug. Now my code is:
it('runs a snipe successfully', async () => {
jest.useFakeTimers()
const exitCode = await john.chat.sendMoneyInChat(channel.topicName, channel.name, "0.01", botUsername);
console.log('timeout?')
jest.advanceTimersByTime(20000)
console.log('timeout.');
console.log('running bal check')
let values;
const nbl = await croupier.checkWalletBalance(process.env.CROUPIER_RINGO_USERNAME);
expect(nbl).toEqual(123);
});
Here's the checkWalletBalance function:
public checkWalletBalance(username: string): Promise<any> {
let balance: number = 0;
const self = this;
return new Promise(async (resolve) => {
try {
const acct = await self.bot1.wallet.lookup(username);
console.log("acct", acct);
const balances = await self.bot2.wallet.balances(acct.accountId);
console.log("balances", balances);
balances.forEach((acctDetail) => {
console.log(acctDetail.balance[0].amount);
balance += parseFloat(acctDetail.balance[0].amount);
});
resolve(balance);
} catch (e) {
console.log(e);
throw e;
}
});
}
I am guessing there is some problem of having async functions inside the Promise?
The Jest teardown starts before console.log("acct", acct) happens within checkWalletBalance, so something's still wrong.
To use/test standard timer functions, you need to instruct jest to use fake timers: jest.useFakeTimers(). Then you need to manually advance time: jest.advanceTimersByTime(msToRun)
EDIT:
Issue is that you declared Promise as async, and not the checkWalletBalance() itself. Changing that should fix it.
Also, to be sure about assertion being checked, you can call expect.assertions(1) at the beginning of test, so jest knows that test must have at least one assertion and won't finish before it's checked.

Dataloader is not working as expected in graphql resolver

I'm having a dataloader along with my graphql like the following:
async function testDataLoader(accountNumber, req, args) {
const dummy = new DataLoader(async accountNumber => {
return new Promise(async (resolve, reject) => {
// rest call
return resolve([<rest result>])
});
});
return dummy.load(accountNumber)
}
export default {
Friends: {
query1: async ({req, args}) => {
const data = await testDataLoader(["12121"], req, args);
// do something with data
}
query2: async ({req, args}) => {
const data = await testDataLoader(["12121"], req, args);
// do something with data
}
}
};
When we query like:
Friends {
query1
query2
}
I expect dataloader to call my rest services only once. However, I could able to see my rest is called twice. Not sure, where I'm making the mistakes.
The issue is that every time you're calling testDataLoader, you're creating a new instance of DataLoader. You should create a single DataLoader instance per request (per resource you're loading). This way every time you call load you're interacting with the same cache.
You could do something like:
const dummy = new DataLoader(...);
async function testDataLoader(accountNumber) {
return dummy.load(accountNumber)
}
But this would persist the DataLoader between requests, which you don't want to do. What you should do is create the DataLoader instance as part of your context, which is recreated each time a request is executed.
const context = async ({ req }) => {
return {
testLoader = new DataLoader(...),
};
},
const server = new ApolloServer({
...
context,
})
Then just call your loader directly inside your resolver:
query2: async (parent, args, context) => {
const data = await context.testLoader.load(["12121"]);
...
}

Using Async/Await with Knex migrations

I am using Knex migrations with regular promise methods, like this:
exports.up = function (knex) {
return knex.schema
.hasTable('table_name')
.then(function (exists) {
if (!exists) {
return knex
.schema
.createTable('table_name', function (table) {
table.increments('id').primary();
})
.then(console.log('created table_nametable'));
}
});
};
How would I refactor this to use async/await? The overall structure, where we are returning knex.schema with a chain of promise methods is throwing me for a loop.
something like this should do:
exports.up = async function (knex) {
if (! (await knex.schema.hasTable('table_name')) ) {
await knex.schema.createTable('table_name', function (table) {
table.increments('id').primary();
});
}
// awaiting sequentially multiple promises to resolve one by one
for (let item of arrayOfStuffToAwait) {
await item;
}
}
exports.down = async function (knex) {
await knex.schema.dropTable('table_name');
}

Resources