I have a single table created on WatermelonDB, in my React Native Android app. When I use batch to insert new records to the table, from a component, I get the error TypeError: Cannot read property 'id' of undefined thrown inside batch.
This is where batch is called with a list of Promise objects:
await database.action(async () => {
const allRecords = this.prepareInsertion(smsTransaction, records);
await database.batch(...allRecords);
console.log(allRecords.length);
});
prepareInsertion(smsTransaction, messages) {
return messages.map(async message => {
try {
return smsTransaction.prepareCreate(transaction => {
const parsedFields = parseFields(message);
transaction.type = parsedFields.type;
transaction.read_at = parsedFields.read_at;
});
} catch (e) {
console.log(e);
}
});
}
Issue was with the async keyword.
return messages.map(message => {
Models to be passed to database.batch, not Promises.
Related
Curious issue I'm having dealing with some callback functions. I need to make a series of API calls that all return promises then I'm trying to take that data and map it to an array that exists on the global scope followed by a function to export the new data as a pdf - my issue is that the then() block is firing before the other function finishes and far before the first API call finshes. `
let fireWatson = async () => {
let watsonClassifed = []
let watsonCallIndex = 0;
let returnedArr = []
for (let i = 0; i < watsonData.length; i++) {
let params = {
classifierId: '***********',
collection: watsonData[i]
}
naturalLanguageClassifier.classifyCollection(params,
function (err, response) {
if (err)
console.log('error:', err);
else
console.log("data returned")
console.log(response.result.collection)
watsonClassifed.push(response.result.collection);
console.log(watsonClassifed)
})
}
}
fireWatson(watsonData).then(res =>
console.log("firing stupid callbback after data")
)
I realize this function isnt actually returning anything but is it possible to still make use of a promise without a return value or is this the main issue im hitting? Ideally - i want the then function to wait until the data is back - mapped to the global array and then outputted but this of course depends on proper synchronicity.
output:
[Done] exited with code=0 in 1.526 seconds
[Running] node "index.js"
firing stupid callbback
data returned
all my sweet sweet data
All functions in JavaScript have returns, it's just that they are implicit if you don't say return explicitly
It's always a bit tricky to mix promises with callbacks. Here is a way you can fireWatson without using any utilities.
let fireWatson = async watsonData => Promise.all(watsonData.map(collection => new Promise((resolve, reject) => {
let params = {
classifierId: '***********',
collection: collection,
}
return naturalLanguageClassifier.classifyCollection(params, function(err, response) => {
if (err) {
reject(err)
} else {
resolve(response)
}
})
})))
Of course, you can simplify this tremendously using a utility I created
const { map } = require('rubico')
let fireWatson = map(collection => new Promise((resolve, reject) => {
let params = {
classifierId: '***********',
collection: watsonData[i]
}
return naturalLanguageClassifier.classifyCollection(params, function(err, response) => {
if (err) {
reject(err)
} else {
resolve(response)
}
})
}))
turns out console.log was firing because every .then() block expects a function.
wrong:
fireWatson(watsonData).then(res =>
console.log("firing stupid callbback after data")
)
right:
fireWatson(watsonData).then(()res =>
console.log("firing stupid callbback after data")
)
So I implemented this code in a file from the react native docs.
class Storage {
//store data in 'key'
store = async (key, data) => {
try {
await AsyncStorage.setItem(key, data);
} catch (error) {
// Error saving data
console.log(error.message);
}
};
retrieve = async (key) => {
try {
const value = await AsyncStorage.getItem(key);
if (value !== null) {
// We have data!!
console.log(value);
}
} catch (error) {
// Error retrieving data
console.log(error.message);
}
};
}
And this in other I want to use to actually store and retrieve the variables:
strg.store('test', 'testing');
testing = strg.retrieve('test');
I kept getting an error but then looking it up here I figured out my storage output was an object and not a string as I expected. So I used JSON.stringify(***) and this gibberish came out instead of a "testing".
{"_40":0, "_65":0, "_55":null,"_72":null}
edit: I figure out how to use the console to debug and I found out the 'testing' was inside the promise object that comes out of my function. I read a little about async functions and now I want to know how do I extract the values from the promises?
This happened because you are using AsyncStorage - an asynchronous storage system. You have to wait until it done retrieve data from storage to get the proper data.
I think there are two correct ways to get data from your implementation:
Use async with your container function name & await with your function called
async function getData() {
....
let data = await strg.retrieve('test');
console.log("data", data);
}
or simple use .then():
strg.retrieve('test').then((data) => {
console.log("data", data);
// Handle retrieved data
});
Hope that help. :)
This is how i did it and it works like a charm
import { AsyncStorage } from 'react-native';
module.exports = {
retrieve: async (value) => {
try {
let data = await AsyncStorage.getItem(value);
return data;
} catch (err) {
return err;
}
},
store: async (key, value) => {
try {
// stringify the value since value can only be string.
if (typeof (value) === 'object')
value = JSON.stringify(value)
return await AsyncStorage.setItem(key, value);
} catch (err) {
console.log(err)
return err;
}
}
}
Your store and retrieve functions are asyn so you have to use await until the actual task is complete.
So the code should be like below.
await strg.store('test', 'testing');
const testing = await strg.retrieve('test');
The garbage value is a promise so it will be something like the object you got.
If you return value like this you will retrieve it from outside.
const value = await AsyncStorage.getItem(key);
if (value !== null) {
// We have data!!
console.log(value);
return value;
}
I'm trying to get all keys from my AsyncStorage database and then filter them in another function, can't seem to get it to wait until AsyncStorage has returned the data?
This function returns the keys in an array:
DATABASE_getAllCoffeeEntries = () => {
AsyncStorage.getAllKeys((err, keys) => {})
.then(keys => {
AsyncStorage.multiGet(keys, (error, items) => {
return items;
}).then(items => {
return items; // array of items is returned
});
});
}
and this function is meant to call the function above then wait for the result then filter down the data.
somefunc = async () => {
var items = await DATABASE_getAllCoffeeEntries();
var someItems = items.filter(function (result, i, item) {
// do filtering stuff
return item;
});
// do something with filtered items
}
Have tried a lot on here but can't get my head around it... any help would be great, thanks.
You need to actually return something from your DATABASE_getAllCoffeeEntries
You could do something like this.
First we wrap your call inside a promise. Which will resolve if we get all the items from AsyncStorage or reject if there is a problem.
Make sure that our calls to AsyncStorage are inside a try/catch. await calls can throw so we need to make sure that we handle any errors.
Use async/await when getting the items from AsyncStorage as this gets ride of the callbacks that are trapping the responses from AsyncStorage. It also can make your code easier to read
Here is the refactor
DATABASE_getAllCoffeeEntries = () => {
return new Promise( async (resolve, reject) => {
try {
let keys = await AsyncStorage.getAllKeys();
let items = await AsyncStorage.multiGet(keys)
resolve(items)
} catch (error) {
reject(new Error('Error getting items from AsyncStorage: ' + error.message))
}
});
}
Then we can call the function like this, though we will have to wrap it in a try/catch as it can throw.
somefunc = async () => {
try {
var items = await this.DATABASE_getAllCoffeeEntries();
var someItems = items.filter(function (result, i, item) {
// do filtering stuff
return item;
});
// do something with filtered items
} catch (error) {
// do something with your error
}
}
I am using redux saga in my React Native app. It all works except for one thing. For one of my actions when it is dispatched the first time, it stops at 'const newUserLogAction = yield take('CREATE_USER_LOG')' in the code below. So the console log 'saga callCreateUserLog take' is not printed, nothing happens. But if I dispatch the same action again, it works. I have other actions and they all work fine the first time.
saga file:
function * createUserLog () {
yield takeEvery('CREATE_USER_LOG', callCreateUserLog)
}
export function * callCreateUserLog () {
try {
console.log('saga callCreateUserLog start')
const newUserLogAction = yield take('CREATE_USER_LOG')
console.log('saga callCreateUserLog take')
console.log('newUserLogAction' + FJSON.plain(newUserLogAction))
const data = newUserLogAction.data
const newUserLog = yield call(api.createUserLog, data)
yield put({type: 'CREATE_USER_LOG_SUCCEEDED', newUserLog: newUserLog})
} catch (error) {
yield put({type: 'CREATE_USER_LOG_FAILED', error})
}
}
action file:
export const createUserLog = (data) => {
console.log('action create user log data = ' + FJSON.plain(data))
return ({
type: 'CREATE_USER_LOG',
data}
)
}
Even on the first dispatch, the data is printed correctly here, so the payload is correct.
react native functions doing the dispatching:
clickContinue = () => {
var event = {
'userId': this.props.user.id,
'eventDetails': {
'event': 'Agreed to the ISA Declaration'
}
}
this.props.dispatch(createUserLog(event))
}
You don't need to use take effect. Action object will be passed by takeEvery.
export function * callCreateUserLog (newUserLogAction) {
try {
console.log('saga callCreateUserLog start')
console.log('newUserLogAction' + FJSON.plain(newUserLogAction))
const data = newUserLogAction.data
const newUserLog = yield call(api.createUserLog, data)
yield put({type: 'CREATE_USER_LOG_SUCCEEDED', newUserLog: newUserLog})
} catch (error) {
yield put({type: 'CREATE_USER_LOG_FAILED', error})
}
}
I am new to ES6 and react-native, trying to get multiple values from the SecureStore.
I think I am misunderstanding promises here ... global.userData is empty in the Promise.all(promises).then function. The relevant values do exist in the secure store
My code is:-
getUserData(fields) {
var promises = [];
var that = this;
global.userData = {};
function getField(field) {
return SecureStore.getItemAsync(field)
.then(res => {
console.log(field+"="+res); // this appears after the log below
global.userData[field] = res;
})
.catch(error => {
global.userData[field] = null;
});
}
fields.map(field => {
promises.push[getField(field)];
});
Promise.all(promises).then(function(v) {
console.log(global.userData); // this is empty
that.setState({ isReady: true }); // allow page to render
});
}
getUserData(["userId", "userName","etc"]);
My bad ... inadvertantly used
promises.push[getField(field)];
should have been:
promises.push(getField(field));
Suprised it wasn't detected as a syntax error ...