I am using React with Meteor. I have a method inside React component that calls Meteor.method (which is run on client as well):
// index.js
loginWithGoogle() {
console.log('1')
Meteor.call('auth.loginWithGoogle', {}, (err, res)=>{
console.log('5');
if (err) {
console.log(err) // line 16:
} else {
console.log('success');
// console.log(res);
// console.log('logged in with google callback in react yay:)')
}
});
}
In client side Meteor I have method:
// auth.js
Meteor.methods({
async 'auth.loginWithGoogle'(){
console.log('2')
let err = await Meteor.loginWithGoogle()
console.log('3');
console.log(err)
if (err) {
console.log('-1')
throw new Error(err);
}
console.log('4');
// Meteor.loginWithGoogle({
// // options
// }, (err) => {
// console.log('3')
// if (err) {
// console.log(err)
// throw new Meteor.Error(err)
// } else {
// console.log('4')
// // successful login!
// }
// });
}
});
Note: Meteor.loginWithGoogle is provided by accounts-google package.
When testing I was able to navigate to google sign in page, sign in, and redirect back to my app) and logs are then printed.
Here the commented code is old approach. Notice, I have console.log calls with numbers, the numbers indicate the order in which I expect code to be executed. The old method does not work at all, console.log('5') runs earlier than (3 and 4), due to asynchronous execution. Rewriting with async/await gives this:
index.js:12 1
auth.js:4 2
auth.js:6 3
auth.js:7 undefined
auth.js:12 4
index.js:14 5
index.js:16 errorClass {isClientSafe: true, error: 404, reason: "Method 'auth.loginWithGoogle' not found", details: undefined, message: "Method 'auth.loginWithGoogle' not found [404]", …}
So, from logs I can see that code is executed as i expected.
Inside auth.js:7 I have err == undefined, but inside index.js (react part) it is errorClass.
How do we deal with async code in Meteor methods?
I got it. Why I was using Meteor.methods on client? I could just use javascript function like that:
const loginWithGoogle = async () => {
console.log('2')
let err = await Meteor.loginWithGoogle()
console.log('3');
console.log(err)
if (err) {
console.log('-1')
throw new Error(err);
}
console.log('4');
}
export {
loginWithGoogle
}
I just use async function, it returns Promise.
Inside React I use async syntax too:
async loginWithGoogle() {
let err = await loginWithGoogle()
if (err){
console.log(err)
}else {
console.log('success')
}
}
Related
I am using NextJS and created three layers that separate the logic.
The purpose is to minimise the error handling to the getServerSideProps section. I want to get all the lists from the database.
In the first layer, in the API route I created a folder called get-all-lists and a file [userId].js. The get request will be 'http://localhost:3000/api/get-all-lists/iudga937gr8'. Bellow there is the api route that get all the lists with the help of Prsima. It is working perfectly
import prisma from '../../../lib/prisma'
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await prisma.List.findMany({ where: { userId: userId }});
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
The next layer, is the abstraction one which sent the final result to getServerSideProps. I created this because I need to fetch alot of requests and it would be too messy...
export const getAllLists = async userId => {
try {
const lists = await axios.get(`/api/get-all-lists/${userId}`);
return lists;
}
catch (error) {
console.log('Abstraction layer error: ', error);
return 'Something went wrong. Please try again later';
}
}
The problem arise here. In the postman I have the right result. In postman I use http://localhost:3000/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d and the server sent me the array specified.
But this function does not work and send me this error:
Abstraction layer error: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:371:5)
at onParseError (node:internal/url:552:9)
at new URL (node:internal/url:628:5)
at dispatchHttpRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:176:20)
at new Promise (<anonymous>)
at http (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:112:10)
at Axios.dispatchRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/dispatchRequest.js:51:10)
at Axios.request (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:142:33)
at Axios.<computed> [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:168:17)
at Function.wrap [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/helpers/bind.js:5:15) {
input: '/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d',
code: 'ERR_INVALID_URL'
}
I also tried to paste the localhost in the browser and it have no problem.
You could extract the functionality into /lib/getAllList.js:
import prisma from './prisma';
export default async function getAllLists(userId) {
return await prisma.List.findMany({ where: { userId: userId }});
}
Then use it in your API route:
import getAllLists from '../../../lib/getAllLists';
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await getAllLists(userId);
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
Then use it in getServerSideProps:
import getAllLists from 'path/to/lib/getAllLists';
export async function getServerSideProps(context) {
const { userId } = context.params;
const shoppingLists = await getAllLists(userId);
return {
props: {
shoppingLists,
},
};
}
I have an existing async function:
async doJSONGetRequest(getUrl, accessToken) {
return new Promise(function(resolve, reject) {
const reqHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
};
console.info('url = ' + getUrl);
request.get({
url: getUrl,
headers: reqHeaders,
}, function(err, response) {
if (err) return reject(err);
try {
// console.debug(`response = ${response.body}`);
const parsed = JSON.parse(response.body);
return resolve(parsed);
} catch (err) {
return reject(err);
}
});
});
}
}
I'm trying to test it with Jasmine(v4).
Of course, I don't want this thing to actually make an HTTP request, so I tried rigging up a spy on the 'request' package's 'get' function in the 'beforeAll' section:
describe('RAPIDAPIService', function() {
beforeAll(async function() {
spyOn(request, 'get')
.and
.callFake(async (parameters) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
return rsp;
} else if (parameters.url === 'http://localhost/api/whoops') {
return new Error('401 not found');
} else {
return null;
}
});
});
it('doJSONGetRequest should run successfully', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/getSomething', '12345678');
expect(data).toEqual('good stuff');
});
it('doJSONGetRequest should resolve errors properly', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/whoops', '12345678');
const expectedError = new Error('401 not found');
expect(res).toEqual(expectedError);
});
Console log statements seem to indicate that I'm actually getting past / returning something from my "await" calls in the "it" tests. But the spies are actually working / detecting that the url's have been called.
(Note that I'm not including here other tests in the same file that do not make asynchronous calls and ARE working... just so you know that there's no problem accessing the actual "api" library and its functions.)
These two tests keep failing with "Error: Timeout - Async function did not complete within 5000ms". And like I said, it seems like they're not returning back to the tests from their calls to the doJSONGetRequest function.
Any thoughts?
Thanks!
I am thinking the issue is the mocking. request.get seems to take two parameters and I am thinking you need to call the 2nd parameter (callback function) once you are done so the resolve can be called.
Try this:
spyOn(request, 'get')
.and
// add callbackFunction as 2nd argument
.callFake((parameters, callbackFunction) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
callbackFunction(null, rsp);
} else if (parameters.url === 'http://localhost/api/whoops') {
callbackFunction({ error: '401 not found' }, {});
} else {
callbackFunction(null, null);
}
});
So I have a simple middleware that listens for errors and removes details to make them friendlier:
import {
isHttpError,
Status,
isProduction,
Middleware
} from "../deps.ts";
import { Context } from "./../types.ts";
const errorMiddleware: Middleware = async (ctx: Context, next: () => Promise<unknown>) => {
try {
await next();
} catch (err) {
let message = err.message;
const status = err.status || err.statusCode || Status.InternalServerError;
/**
* considering all unhandled errors as internal server error,
* do not want to share internal server errors to
* end user in non "development" mode
*/
if (!isHttpError(err)) {
message = !isProduction
? message
: "Internal Server Error";
}
if (!isProduction) {
console.log(err);
}
ctx.response.status = status;
ctx.response.body = { status, message };
}
};
export { errorMiddleware };
I'm trying to write a test that throws a random error and checks to see if the middleware is adjusting the message as expected.
This is what I've tried:
Deno.test({
name: "Middleware changes error message to Internal Server Error",
async fn() {
const mw1: Middleware = async (_context, next) => {
await next();
};
let caught: any;
const mockContext = testing.createMockContext();
const mockNext = () => {
return new Promise<void>((resolve) => {
setTimeout(() => {
mockContext.throw(500, "Hi there");
resolve();
}, 50);
});
};
try {
await mw1(mockContext, mockNext); // hoping this throws the error into the next middleware
await errorMiddleware(mockContext, testing.createMockNext());
} catch (error) {
caught = error;
}
assert(caught instanceof httpErrors.InternalServerError);
assertEquals(caught.message,"Internal Server Error");
},
});
The test doesn't work, I just get this error:
Is there a different way I should be approaching the tests? I've tried to look at the tests in the oak library to see if there's similar examples but I couldn't seem to find any.
It's not possible to reproduce your issue because you have not provided all of the necessary code, so here's a similar, self-contained example to demonstrate how to test an error renaming middleware with Oak. Also, see the internal Oak middleware module tests for reference.
so-69820660.test.ts
import { delay } from "https://deno.land/std#0.113.0/async/mod.ts";
import {
assert,
assertEquals,
} from "https://deno.land/std#0.113.0/testing/asserts.ts";
import {
composeMiddleware,
httpErrors,
Middleware,
Status,
testing,
} from "https://deno.land/x/oak#v9.0.1/mod.ts";
const renameError: Middleware = async (_, next) => {
try {
await next();
} catch (exception: unknown) {
if (!(exception instanceof Error)) throw new Error(String(exception));
exception.message = "Custom error message";
throw exception;
}
};
Deno.test({
name: "Middleware renames error message",
async fn() {
let exceptionWasCaught = false;
const delayAndThrow: Middleware = async (ctx) => {
await delay(50);
ctx.throw(Status.InternalServerError, "Uh oh");
};
const catchAndAssert: Middleware = async (_, next) => {
try {
await next();
} catch (exception: unknown) {
exceptionWasCaught = true;
assert(exception instanceof httpErrors.InternalServerError);
assertEquals(exception.message, "Custom error message");
}
};
const mw = composeMiddleware([catchAndAssert, renameError, delayAndThrow]);
await mw(testing.createMockContext());
assert(exceptionWasCaught);
},
});
% deno --version
deno 1.15.3 (release, x86_64-apple-darwin)
v8 9.5.172.19
typescript 4.4.2
% deno test so-69820660.test.ts
running 1 test from file:///Users/deno/so-69820660.test.ts
test Middleware renames error message ... ok (65ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (261ms)
I have a cloud code from which I call an external function.
The cloud code response is null but the console displays the response
my cloud code ;
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
externalFunction (ccaddrequest, function (err, result) {
console.log(result);
return result;
}) ;
} catch (e) {
console.log("Error");
}
});
console.log (result); shows the values from the external function, but the return result; returns null
how can I get the external function response as response of my cloud code function ?
The problem is that your externalFunction uses a callback to return its result. That is an asynchronous event, meaning that it happens after your cloud functions has been processed.
The cloud function will execute var ccaddrequest... and then call externalFunction but it won't "wait" for externalFunction to call the callback function if it contains asynchronous commands.
So you need to wrap the externalFunction in a Promise (see how to promisify callbacks) and then await the result of it.
Plus you need to return the result of the Promise, so in your code you need to add
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
var result = await externalFunctionPromise(...);
return result;
} catch (e) {
console.log("Error");
}
});
Should HTTPS functions return asynchronous promises like realtime functions have to?
We haven't been returning in HTTPS functions (just using res.status.send etc), and it looks like firebase/function-samples aren't either. But the documentation is slightly ambiguous https://firebase.google.com/docs/functions/terminate-functions .
This works now in the latest Firebase:
exports.asyncFunction = functions.https.onRequest(async (request, response) => {
const result = await someAsyncFunction();
response.send(result);
});
HTTP functions currently do not respect returned promises - they require a sent result in order to terminate normally. If an HTTP function doesn't send a result, it will time out.
All other types of functions require a returned promise in order to wait for asynchronous work to fully complete.
If you don't have any async work to wait for, you can just return immediately.
These are the three cases outlined in the docs.
After much looking around , this is implementation with a Promise worked for me to return a value from a Google Cloud Function where the function needs to make a third-party asynchronous call :
exports.getSomeAccessToken = functions.https.onCall((data, context) => {
var dataStr = JSON.stringify(data, null, '\t');
console.log('ENTER [getSomeAccessToken], got dataStr: ' + dataStr);
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, gatewayResponse) {
var result = {
clientToken: gatewayResponse.clientToken
};
var resultStr = JSON.stringify(result, null, '\t');
console.log("resultStr : " + resultStr);
resolve(result);
});
});
});
Your cloud functions should return"end" with either of the following
res.redirect(), res.send(), or res.end()
What they mean by returning promises, is lets imagine you have a cloud function that updated a node in your realtime database, you would like to complete that work before responding to the HTTP request.
Example code
let RemoveSomething = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Remove something
DoDatabaseWork()
.then(function (result) {
res.status(200).send();
})
.catch(function (err) {
console.error(err);
res.status(501).send();
});
});
});
Update: Added DoDatabaseWork example.
const DoDatabaseWork = function () {
return new Promise(function (resolve, reject) {
// Remove SomeNode
admin.database().ref('/someNode/').remove()
.then(function (result) {
resolve();
})
.catch(function (err) {
console.error(err);
reject();
});
});
}