Error Message: Function WriteBatch.set() called with invalid data. Data must be an object, but it was: www.google.com
I have a button that onclick runs the addQuotes function.
Where am I going wrong?
const addQuotes = async () => {
let test = ['https://www.google.com/', 'https://www.google.com/about',]
const obj1 = Object.assign({}, test);
const batch = writeBatch(db);
Object.keys(obj1).forEach(key => {
var docRef = db.collection("col").doc(); //automatically generate unique id
batch.set(docRef, obj1[key]);
});
batch.commit().then(function () {
console.log("hello")
});
}
As the error says, the data param should be an object (second argument to the batch.set method)
Commented out the firebase methods invocation for running the code
and show you the o/p
// In your case when you do
let test = ['https://www.google.com/', 'https://www.google.com/about', ]
const obj1 = Object.assign({}, test);
console.log(obj1);
// and ignoring the unique id step
Object.keys(obj1).forEach((key, index) => {
// var docRef = db.collection("col").doc(); //automatically generate unique id
console.log(obj1[key]); // here it is just the string value
console.log({[index]: obj1[key]}); // here it is an object
//when you do the bellow line
// batch.set(docRef, obj1[key]); // value of obj1[key] is just string which is not the correct type
// may be you do something as below (key can be as you want it to be)
// batch.set(docRef, {[index]: obj1[key]}); // index is added as key for the object which in now an object and should work
});
I used {[index] : obj1[key]} notation which is computed property in object
Hope it's clear 🙂
Related
So I'm working on a project where I'm making a call to a database to retrieve the data stored there. This data comes as an array. here is the code:
const allLogins = await Login.find().sort("name");
const token = req.header("x-auth-token");
const user = jwt.verify(token, config.get("jwtPrivateKey"));
const logins = allLogins
.filter((login) => login.userId === user._id)
.map((login) => {
login.password = decrypt(login.password);
});
If I call a console.log after the decrypt has been run I see that it has been completed correctly. The issue I have is if I console.log(logins) it says it is an array of two items that are both undefined. If instead I run it like this...
const allLogins = await Login.find().sort("name");
const token = req.header("x-auth-token");
const user = jwt.verify(token, config.get("jwtPrivateKey"));
let logins = allLogins.filter((login) => login.userId === user._id);
logins.map((login) => {
login.password = decrypt(login.password);
});
Then it works as it should. I'm not sure why the first set of code doesn't work and why the second set does work.
Any help would be appreciated!
Basic :
array. filter - accept a callback and call back return boolean (that match our criteria)
array.map - accept a callback and call back return transformed object
In the second working example:
logins.map((login) => {
// note: logins is iterated but not assigned to logins back
// so accessing login is working
login.password = decrypt(login.password); // map should return data
+ return login; // if we update all code will work
});
Now coming to first example:
const logins = allLogins
.filter((login) => login.userId === user._id)
.map((login) => {
login.password = decrypt(login.password);
+ return login; // this will fix the issue
});
Using the ipfs-http-client I can successfully add data using the below method. The console.log(added) returns an object with the path, cid, and size keys.
However, the console.log(exists) line returns Object [AsyncGenerator] {}.
I would like to be able to check if a data string exists. Is this possible?
import { create as ipfsHttpClient } from 'ipfs-http-client'
const ipfsClient = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0')
const handleData = async (data) => {
const added = await ipfsClient.add(data)
console.log(added)
const exists = await ipfsClient.get(data)
console.log(exists)
}
handleData('hello world')
The get method returns AsyncIterable<Uint8Array> object, which may be what you're printing out. To get each bytes, you will have to loop over it:
const cid = 'QmQ2r6iMNpky5f1m4cnm3Yqw8VSvjuKpTcK1X7dBR1LkJF'
for await (const buf of ipfs.get(cid)) {
// do something with buf
console.log(buf);
}
If all you care about is whether the data exists, you can just call next() method on the iterator and check for null or error.
const exists = (await ipfs.get(cid)).next() !== null
I am trying something simple with a Chrome Extension, but the documentation I have found is not clear enough regarding the structure of the methods to use the chrome storage. I have the following code (generic) to store and retrieve some values:
var value = 561;
var key = "abc";
chrome.storage.sync.set({ [key] : value });
chrome.storage.sync.get(key, ({ result }) => {
console.log("value received is: " + result); // This does not work, result = undefined
});
If I want to retrieve the key added to the store, what is wrong with the previous code?
When using { result } you are extracting result from the first parameter being passed.
Should be instead:
var value = 561;
var key = "abc";
chrome.storage.sync.set({[key]: value });
chrome.storage.sync.get(key, ({ abc }) => {
console.log("value received is: " + abc);
});
// or
chrome.storage.sync.get(key, (result) => {
console.log("value received is: " + result.abc);
});
TLDR;
In JavaScript, when adding { result } you are extracting that variable from the underlying object in this case, the parameter.
Assuming the object is:
const results = {
a: 1,
b: 2,
c: 3
}
You can simplify the parameters to be like:
const { a, b ,c } = results
which is similar to:
const a = results.a
const b = results.b
const c = results.c
Since the first argument in your function is now { result }, it is expecting that key to exist in the first argument of that function. But it doesn't, it should be instead{abc}
I am trying to store an object, mapping a string to a list, using chrome.sync.get. My goal is to create a new list for a non-existing key or append an element to the list if the key exists. However, I am unable to populate the object. When I try to retrieve the values I have previously inserted, I get an empty Object as the returned value. Following is the code I am using:
let currentTabId = '234';
let spawnedTabId = '390';
chrome.storage.sync.get(currentTabId, function(data) {
if (typeof data.currentTabId === 'undefined') {
chrome.storage.sync.set({currentTabId: [spawnedTabId]}, function() {
console.log("Initialized "+currentTabId+" with "+spawnedTabId);
});
chrome.storage.sync.get(currentTabId, function(data) {
console.log(data);
});
} else {
data.currentTabId.push(spawnedTabId)
chrome.storage.sync.set({currentTabId: data.currentTabId}, function() {
console.log("Appended "+spawnedTabId+" to "+currentTabId);
});
}
});
The output I am getting is:
>>> Initialized 234 with 390
>>> {}
__proto__: Object
The code had three mistakes:
incorrect use of a variable to make an object literal,
instead of {variable: value} it should be {[variable]: value}, more info
incorrect use of a variable to read a property from an object,
instead of obj.variable it should be obj[variable]
incorrect use of asynchronous API,
the data should be read after it's written i.e. inside the callback.
let key = '234';
let spawnedTabId = '390';
chrome.storage.sync.get(key, data => {
const spawned = data[key] || [];
spawned.push(spawnedTabId);
chrome.storage.sync.set({ [key]: spawned }, () => {
// now you can read the storage:
// chrome.storage.sync.get(key, console.log);
});
});
I'm just starting to play around with Puppeteer (Headless Chrome) and Nodejs. I'm scraping some test sites, and things work great when all the values are present, but if the value is missing I get an error like:
Cannot read property 'src' of null (so in the code below, the first two passes might have all values, but the third pass, there is no picture, so it just errors out).
Before I was using if(!picture) continue; but I think it's not working now because of the for loop.
Any help would be greatly appreciated, thanks!
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let article = document.querySelector('.c-entry-content').innerText;
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
let source = "The Verge";
let categories = "Tech";
if (!picture)
continue; //throws error
return {
title,
article,
picture,
source,
categories
}
});
}
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
If there is no picture, then document.querySelector() returns null, which does not have a src property. You need to check that your query found an element before trying to read the src property.
Moving the null-check to the top of the function has the added benefit of saving unnecessary calculations when you are just going to bail out anyway.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
const pictureElement = document.querySelector('.c-picture img');
if (!pictureElement) return null;
const picture = pictureElement.src;
const title = document.querySelector('h1').innerText;
const article = document.querySelector('.c-entry-content').innerText;
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
if (!result) continue;
// ... do stuff with result
}
Answering comment question: "Is there a way just to skip anything blank, and return the rest?"
Yes. You just need to check the existence of each element that could be missing before trying to read a property off of it. In this case we can omit the early return since you're always interested in all the results.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
const result = await page.evaluate(() => {
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1');
const content = document.querySelector('.c-entry-content');
const picture = img ? img.src : '';
const title = h1 ? h1.innerText : '';
const article = content ? content.innerText : '';
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
// ...
}
}
Further thoughts
Since I'm still on this question, let me take this one step further, and refactor it a bit with some higher level techniques you might be interested in. Not sure if this is exactly what you are after, but it should give you some ideas about writing more maintainable code.
// Generic reusable helper to return an object property
// if object exists and has property, else a default value
//
// This is a curried function accepting one argument at a
// time and capturing each parameter in a closure.
//
const maybeGetProp = default => key => object =>
(object && object.hasOwnProperty(key)) ? object.key : default
// Pass in empty string as the default value
//
const getPropOrEmptyString = maybeGetProp('')
// Apply the second parameter, the property name, making 2
// slightly different functions which have a default value
// and a property name pre-loaded. Both functions only need
// an object passed in to return either the property if it
// exists or an empty string.
//
const maybeText = getPropOrEmptyString('innerText')
const maybeSrc = getPropOrEmptyString('src')
async function scrape3() {
// ...
// The _ parameter name is acknowledging that we expect a
// an argument passed in but saying we plan to ignore it.
//
const evaluate = _ => page.evaluate(() => {
// Attempt to retrieve the desired elements
//
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1')
const content = document.querySelector('.c-entry-content')
// Return the results, with empty string in
// place of any missing properties.
//
return {
title: maybeText(h1),
article: maybeText(article),
picture: maybeSrc(img),
source: 'The Verge',
categories: 'Tech'
}
}))
// Start with an empty array of length 3
//
const evaluations = Array(3).fill()
// Then map over that array ignoring the undefined
// input and return a promise for a page evaluation
//
.map(evaluate)
// All 3 scrapes are occuring concurrently. We'll
// wait for all of them to finish.
//
const results = await Promise.all(evaluations)
// Now we have an array of results, so we can
// continue using array methods to iterate over them
// or otherwise manipulate or transform them
//
results
.filter(result => result.title && result.picture)
.forEach(result => {
//
// Do something with each result
//
})
}
Try-catch worked for me:
try {
if (await page.$eval('element')!==null) {
const name = await page.$eval('element')
}
}catch(error){
name = ''
}