Dark magic going through res.json() - javascript

To avoid the XY problem, here's my final goal: I want to fetch something, use the response body, and return the response (from which the user should be able to get the body) without returning the body separately. On the paper, this should work:
const res = await fetch("something")
const clone = res.clone()
const body = await clone.json()
// Use body
return res
If I get lucky, the process ends during await clone.json(). If I don't, it freezes. Forever.
As a reminder, res.json can't be called twice.
Does it think I'm not good enough to get an error message? Any help on that? Thank you.
Progess
I located the error: in node_modules/node-fetch/lib/index.js:416, the listener on the end of the action is never triggered. I still don't know why.
When replacing the URL with "https://jsonplaceholder.typicode.com/todos/1", everything works. This may be related to the server. I don't see any link between the origin of the request and whether the request could be cloned...
Placing res.text() before clone.json() magically fixes it, but it makes everything lose its purpose.
By messing with the module's code, I found that the response actually never ends. The JSON gets cut, and the chunk from the last data event call isn't the end of the JSON, so the end event never gets called. Extremely confusing.
I spent too much time on this issue, I will simply avoid it by returning the body separately.

This seems to work just fine
const myFetch = async (url) => {
const resp = await fetch(url),
clone = resp.clone()
json = await clone.json()
console.log('inner:', json.title)
return resp
}
myFetch('https://jsonplaceholder.typicode.com/todos/1')
.then(resp => resp.json())
.then(json => console.log('outer:', json.title))

Related

Extract the price only from api using node-fetch array

I am sorry for a basic question, I have been trying to extract only the price using node-fetch from API
const fetch = require('node-fetch');
fetch('https://api.binance.us/api/v3/avgPrice?symbol=DOGEUSD')
.then(res => res.text())
.then(text => console.log(text))
let AvgPrice = text.map(text => text.price);
The error I am receiving is
internal/modules/cjs/loader.js:968
throw err;
^
Please, any suggestion is greatly appreciated
There are several things that you need to check out
Errors reagarding cjs/loader.js have little or nothing to do with your code per se but rather the setup, for example how you run the code, naming of the file, etc,
internal/modules/cjs/loader.js:582 throw err
https://github.com/nodejs/help/issues/1846
This code will return Reference error: text is not defined.
The reason is that you never define the variable text and then you try to call map function on it.
Also fetch is a async function and nodejs is single threaded non-blocking. So what happens is that you send a http request (fetch) to the website, that takes times, but meanwhile your coding is still running, so continues to the next line in your code.
Lets add some console logs
const fetch = require('node-fetch');
console.log('1. lets start')
fetch('https://api.binance.us/api/v3/avgPrice?symbol=DOGEUSD')
.then(res => res.text())
.then(text => {
console.log('2. I got my text', text)
})
console.log('3. Done')
You might think that this will log out
lets start
I got my text {"mins":5,"price":"0.4998"}
Done
Nopes, it will log out
Lets start
Done
I got my text {"mins":5,"price":"0.4998"}
Because you fetched the data, then your program continued, it looged out 3. Done and THEN when it got the data from api.binance it logged out 2. I got my text (notice the keyword then, it happens later)
map is a function for arrays. What the api returns is an object. So when you fix your async code, you will get TypeError text.map is not a function
Since it returns an object you can access it's property right away text.price

How to wait for requests and validate responses using playwright?

This is my first time using playwright and I can't figure out how to wait for requests and validate responses.
I've been using cypress for a quite a long time, and it was pretty easy to manage network requests.
For example, I need to validate response after clicking a button and this is how I would do it with cypress:
cy.server()
cy.route('POST', '/api/contacts').as('newContact')
cy.get('.btn').click()
cy.wait('#newContact').then((response) => {
expect(response.status).to.eq(400)
expect(response.responseBody.data.name).to.eq('abcde')
})
And this is how I'm trying to do the same thing with playwright, but it validates GET request that was sent long before it even clicked save button. I can't figure out how to manage this request properly and that's a show stopper for my test suite:
await contacts.clickSaveBtn()
await page.waitForResponse((resp) => {
resp.url().includes('/api/contacts')
expect(resp.status()).toBe(400)
})
Any help or advice would be really appreciated
What you need to do is first start waiting for the response and then click, so the waitForResponse() can catch the actual response coming as a result of the click.
await Promise.all([
page.waitForResponse(resp => resp.url().includes('/api/contacts') && resp.status() === 400),
contacts.clickSaveBtn()
]);
This should handle the possible race condition.
Alternatively, you can assign a promise, then later wait for it:
const responsePromise = page.waitForResponse(resp => resp.url().includes('/api/contacts') && resp.status() === 400);
await contacts.clickSaveBtn();
const response = await responsePromise;
It's more readable and you get the response value.

Async API call inside an infinite while loop using Axios in NODE JS

Let me begin by saying that I am a beginner to Asynchronous JavaScript and it took me quite a while to wrap my head around the concept.
Now, coming to the situation at hand. I wanted to write a code that would automatically detect text from a folder containing multiple screenshots using an OCR API, whose documentation can be found here : https://ocr.space/OCRAPI
The OCR uses two engines which can produce different results. The code should run with the default engine specified and display the contents. If the result is satisfactory, the user can chose to move on with the next screenshot, or make another api request with a different engine.
I tried to use an infinite while loop to achieve this:
const fs = require("fs")
const imageToBase64 = require("image-to-base64")
const apikey = "helloworld"
let engine = 1
fs.promises
.readdir("./screenshots")
.then((files) => {
for (const file of files) {
while (true) {
console.log("Making an API Request")
imageToBase64("./screenshots/" + file)
.then((res) => {
const form = new FormData()
form.append("base64Image", "data:image/jpeg;base64," + res)
form.append("OCREngine", engine)
axios
.post("https://api.ocr.space/parse/image", form, {
headers: { apikey, ...form.getHeaders() },
})
.then((response) => {
console.log(response.data.ParsedResults[0].ParsedText)
/*Code to ask the user whether the result is satisfactory or not.
if yes, break out of the while loop, otherwise change the engine and make the request again*/
engine = 2
})
.catch((error) => console.log(error))
})
.catch((error) => console.log(error))
}
}
})
.catch((error) => console.log(error))
The infinite while loop appears to continue the execution and not wait for the user input.
Can anyone help me out with the correct method of implementing this idea?

Maintaining state within a conversation within DialogFlow

I am trying to understand how I can manage some aspect of state within fullfillment (DialogFlow's implementation where you can write code in JavaScript and it executes within a Google Cloud Function). First, I would assume that this implementation is stateless, but there must be a way to maintain some state without having to store the data in the database and then retrieve it on the next execution.
I would simply like to maintain the full history of the chat - the question asked by the user, and the response from the chatbot. I can see that I can get this information on every response (and call to the fullfillment) via:
console.log(JSON.stringify(request.body.queryResult.queryText));
console.log(JSON.stringify(request.body.queryResult.fulfillmentText));
Now that I have this information I just want to append it to some variable that is statefull. I have looked into setContext, context.set, app.data, and other functions/variables, but I can't seem to get it working because I'm not sure I understand how it should work.
In my code I have mostly the basic template. I don't think I can use a global variable so how can I store this state (fullConversation) between intent executions just for this user's conversation?
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
let query = JSON.stringify(request.body.queryResult.queryText);
let response = console.log(JSON.stringify(request.body.queryResult.fulfillmentText);
// here I want to retrieve the prior query/response and append it
// i.e., let fullConversation = fullConversation + query + response
}
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function myNewHandler(agent) {
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('myIntent',myNewHandler);
agent.handleRequest(intentMap);
});
UPDATE:
If I update my code with the code suggestion from #Prisoner I'm still having issues with just getting the context. I never get to my console.log(2). Do I need to move the agent.context.get code outside of the onRequest block??:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
console.log(JSON.stringify(request.body.queryResult.queryText));
console.log(JSON.stringify(request.body.queryResult.fulfillmentText));
console.log("1");
// Get what was set before, or an empty array
const historyContext = agent.context.get('history');
console.log("2");
SECOND UPDATE:
The issue is related to a known issue solved here.
Just needed to update the dialogflow-fulfillment in package.json and everything worked.
You're on the right track. Global variables are definitely not the way to do it. And state can be maintained as part of a Context.
The app.data property is only available if you're using the actions-on-google library, which it does not look like you're using. Several of the APIs have also changed over time, and can be confusing. See this older answer for an examination of some of the options.
Since you're using the dialogflow-fulfillment library, you'll be using the agent.context (note the singular) object to add new contexts. For the context, you'll want to set a context parameter with the value that you want to store. Values need to be strings - so if you have something like an array, you probably want to convert it to a string using something like JSON.serialzie() and extract it with JSON.parse().
The code that gets the current context with your stored information, and then updates it with the latest values, might look something like this:
// Get what was set before, or an empty array
const historyContext = agent.context.get('history');
const historyString = (historyContext && historyContext.params && historyContext.params.history) || '[]';
const history = JSON.parse(historyString);
// Add the messages as a single object
history.push({
requestMessage,
responseMessage
});
// Save this as a context with a long lifespan
agent.context.set('history', 99, JSON.stringify(history));
Update
I would put this code in a function, and call this function before you return from each handler function you're in. I'm a little surprised that agent.context would be causing problems outside the handler - but since you don't seem to have any specific error, that's my best guess.

The click action in mailinator page does not work with protractor

I'm trying to automate the verification code sent to an email in mailinator, when I run the test therror is: "TimeoutError: Wait timed out after 35001ms", I'm thinking that is a problem with the async functions but I'm not secure about that.
const emailRow = element(by.className("tr.even.pointer.ng-scope"));
this.setCode = async function() {
let windows = await browser.getAllWindowHandles();
await browser.switchTo().window(windows[1]);
await browser.wait(ExpectedConditions.visibilityOf(emailRow), 50000);
browser.actions().mouseMove(emailRow).click().perform();
await browser.wait(ExpectedConditions.visibilityOf(emailCode), 35000);
}
I also tried this
this.setCode = async function() {
let windows = await browser.getAllWindowHandles();
await browser.switchTo().window(windows[1]);
await browser.wait(ExpectedConditions.elementToBeClickable(emailRow), 50000);
emailRow.click();
await browser.wait(ExpectedConditions.visibilityOf(emailCode), 35000);
}
But I have the same problem, in the screen I can't see that the test perform the click, I put an sleep after the click in the emailRow but doesn't work, in the image there is the page that i want to perform the click.
I think your best bet is to use their api, instead of going to their website and read the email there. In protractor, this is very easy. depending on whether or not you have a premium account with them you can use a public or a private team inbox. In the case of a public inbox, perform something similar to the following:
const checkMailinatorPublicEmail = async () => {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let requestUrl = 'https://mailinator.com/api/v2/domains/public/inboxes/your_inbox_name_here';
let responseBody = await fetch(requestUrl);
let responseJson = await responseBody.json();
return responseJson;
}
Now, you have all the email in the inbox in your response body as a json object. To keep this simple, do not use a static public team inbox, instead use a random inbox name so that each time you will only have one email in the inbox and you can parse that email to your needs.
I believe you should try the second approach. In the first approach, you are waiting for an element to be visible that does not guarantee an element is clickable.
By looking at second approach, the code looks fine. My suggestion is to try changing click method like below
browser.executeScript('arguments[0].click()', emailRow.getWebElement());
Hope this will help
Happy coding!

Categories