Hello I am stuck with indexedDB. When I try to store an array of links, it fails with no errors or exceptions.
I have two code examples. This one works fine:
export const IndexedDB = {
initDB(): Promise<string> {
return new Promise((resolve, reject) => {
const open = indexedDB.open("MyDB", 1);
open.onupgradeneeded = function () {
open.result.createObjectStore("store", { keyPath: "id" });
resolve("Database initialized successfully...");
};
open.onerror = function (err) {
reject(err);
};
open.onsuccess = function () {
resolve("Database initialized successfully...");
};
});
},
getAll(): Promise<Values> {
return new Promise((resolve, reject) => {
const open = indexedDB.open("MyDB", 1);
open.onerror = function (err) {
reject(err);
};
open.onsuccess = function () {
const tx = open.result.transaction("store", "readonly");
const store = tx.objectStore("store");
const getFromStore = store.getAll();
getFromStore.onerror = function (err) {
reject(err);
};
getFromStore.onsuccess = function () {
resolve(getFromStore.result);
};
};
});
},
putMany(values: Values): Promise<void> {
return new Promise((resolve, reject) => {
const open = indexedDB.open("MyDB", 1);
open.onerror = function (err) {
reject(err);
};
open.onsuccess = function () {
values.forEach((value: Value) => {
const tx = open.result.transaction("store", "readwrite");
const store = tx.objectStore("store");
const putInStore = store.put(value);
putInStore.onerror = function (err) {
reject(err);
};
});
resolve();
};
});
},
};
App.tsx:
export const saveValues = (values: Values): Promise<void> => {
IndexedDB.putMany(values);
};
export const App: React.FunctionComponent = () => {
const valuesForTest: Values = [
{
id: Math.random(),
text: "Hello world!"
},
{
id: Math.random(),
text: "Hello world!"
},
{
id: Math.random(),
text: "Hello world!"
}
];
useEffect(() => {
saveValuesToDB(valuesForTest);
}, []);
return (
<SomeComponent/>
);
};
And this one always fails. No errors or exceptions at all. The data just isn't saved.
The main difference is just data types. The first example uses the "Values" type, and the second uses the "Links" type.
export const IndexedDB = {
initDB(): Promise<string> {
return new Promise((resolve, reject) => {
const open = indexedDB.open("MyDB", 1);
open.onupgradeneeded = function () {
open.result.createObjectStore("store", { keyPath: "href" });
resolve("Database initialized successfully...");
};
open.onerror = function (err) {
reject(err);
};
open.onsuccess = function () {
resolve("Database initialized successfully...");
};
});
},
getAll(): Promise<Links> {
return new Promise((resolve, reject) => {
const open = indexedDB.open("MyDB", 1);
open.onerror = function (err) {
reject(err);
};
open.onsuccess = function () {
const tx = open.result.transaction("store", "readonly");
const store = tx.objectStore("store");
const getFromStore = store.getAll();
getFromStore.onerror = function (err) {
reject(err);
};
getFromStore.onsuccess = function () {
resolve(getFromStore.result);
};
};
});
},
putMany(values: Links): Promise<void> {
return new Promise((resolve, reject) => {
const open = indexedDB.open("MyDB", 1);
open.onerror = function (err) {
reject(err);
};
open.onsuccess = function () {
values.forEach((value: Link) => {
const tx = open.result.transaction("store", "readwrite");
const store = tx.objectStore("store");
const putInStore = store.put(value);
putInStore.onerror = function (err) {
reject(err);
};
});
resolve();
};
});
},
};
App.tsx:
export const saveValues = (values: Links): Promise<void> => {
IndexedDB.putMany(values);
};
export const App: React.FunctionComponent = () => {
const valuesForTest: Links = [
{
href: "https://somelink1.com/",
name: "Link"
},
{
href: "https://somelink2.com/",
name: "Link"
},
{
href: "https://somelink3.com/",
name: "Link"
},
];
useEffect(() => {
saveValuesToDB(valuesForTest);
}, []);
return (
<SomeComponent/>
);
};
Can you help me?
If you created the database with the first code snippets, your "store" object store will be using id as keyPath. If you want to change this to href as in your second snippet, delete the database first (in the browser devtools), then run your second snippet.
Kindly note that if you want to make schema changes to an existing database (without deleting it), you will have to pass a higher version number to indexedDB.open to trigger your onupgradeneeded callback: indexedDB.open("MyDB", 2);. If the database exists with the same version, that callback will (obviously) not be executed because it doesn't need upgrading.
Also note that if you want to use both your code snippets to store different things (values and links), use separate object stores for them: createObjectStore("values", { keyPath: "id" }) and createObjectStore("links", { keyPath: "href" })
Last but not least, for easier IndexedDB usage I can really recommend to use AceBase (fullblown realtime database) or Dexie (simple IndexedDB wrapper) instead. Those are easy to use and handle all IndexedDB plumbing!
EDIT: In your putMany method I also see you call resolve in the open.onsuccess callback, instead of waiting until all of your put operations have succeeded. That's why you don't get an error, reject will be called after resolve so that does nothing.
Related
When entering a screen, 5 promises are loaded automatically, I use a promise.all, the problem is that they are executed randomly, within each function I use a push where I put the information.
The problem is that I have to change the push for a splice because the promise.all is loaded randomly and with the push I can't know which place to assign to each information of each "function". Here is my code:
At the beginning it loads the promises
ngOnInit(): void {
Promise.all([this.getData1(), this.getData2()]).then(values => {
console.log(values)
this.processing = true;
}).catch(reason => {
console.log('error get data',reason)
});
}
I only put 2 as an example but in the other functions it is the same
public getData1() {
return new Promise((resolve, reject) => {
this.createService.getServiceData1().subscribe(
(response: any) => {
let customFieldOption: CustomFieldOption = new CustomFieldOption();
this.opcionServicio = response;
this.opcionesServicio.push(this.opcionServicio);
this.servicio.push(this.opcionesServicio[0].ticket_field.title)
customFieldOption.id = this.opcionServicio.ticket_field.id;
customFieldOption.name = this.opcionServicio.ticket_field.title;
this.customFieldOptions.push(customFieldOption);
resolve(true);
},
(error) => {
console.log(error);
reject(true);
}
);
});
}
public getData2() {
return new Promise((resolve, reject) => {
this.createService.getServiceData2().subscribe(
(response: any) => {
let customFieldOption: CustomFieldOption = new CustomFieldOption();
this.opcionServicio = response;
this.opcionesServicio.push(this.opcionServicio);
this.servicio.push(this.opcionesServicio[0].ticket_field.title)
customFieldOption.id = this.opcionServicio.ticket_field.id;
customFieldOption.name = this.opcionServicio.ticket_field.title;
this.customFieldOptions.push(customFieldOption);
resolve(true);
},
(error) => {
console.log(error);
reject(true);
}
);
});
}
You can use an array with indexes instead of push or an object with static keys.
Example 1:
You can put data1 to this.opcionesServicio[0] and data2 to this.opcionesServicio[1]. Then you know that they can be always accessed by the same index.
opcionesServicio = [];
public getData1(dataIndex = 0) {
return new Promise((resolve, reject) => {
this.createService.getServiceData1().subscribe((response: any) => {
this.opcionesServicio[dataIndex] = respoosne;
});
});
}
public getData2(dataIndex = 1) {
return new Promise((resolve, reject) => {
this.createService.getServiceData2().subscribe((response: any) => {
this.opcionesServicio[dataIndex] = respoosne;
});
});
}
// Access
const data1 = this.opcionesServicio[0];
const data2 = this.opcionesServicio[1];
Example 2:
You can store the data in an object instead.
data1 goes to this.opcionesServicio['data1'] and data2 to this.opcionesServicio['data2']. Then you can access them by the data1, data2 keys.
opcionesServicio = {};
public getData1(dataName = 'data1') {
return new Promise((resolve, reject) => {
this.createService.getServiceData1().subscribe((response: any) => {
this.opcionesServicio[dataName] = respoosne;
});
});
}
public getData2(dataName = 'data2') {
return new Promise((resolve, reject) => {
this.createService.getServiceData2().subscribe((response: any) => {
this.opcionesServicio[dataName] = respoosne;
});
});
}
const data1 = this.opcionesServicio['data1'];
const data2 = this.opcionesServicio['data2'];
I am trying to call some function using a single express router , I want to call them in order, meaning that I don't want getLaps() function to execute before get streams function has done all the work , so I tried to use some solutions I found on the internet but it didn't work, the second function doesn't execute. Please help.
Here is my code :
router.get("/", async (req, res,done) => {
res.status(201).send('created user')
return getLaps(function () {
getStreams(function () {
});
});
// await getStreams();
// await getLaps();
// console.log("hey")
});
Here is the get laps function :
function getLaps(req) {
const access_token = '75f2d92fdc445033312854d775e039b6c5bf04e7';
//for test 3756582581,
const idL = [5567017025, 5566531480];
const stravaClient = StravaClientService.getClient(access_token);
const activityService = StravaActivityService(stravaClient);
var params = {
TableName: "run-id",
Key: {
"id": "15428785",
}
};
console.log("cool laps")
docClient.get(params, async function (err, data) {
if (err) {
console.log("Error", err);
} else {
}
idL.map((id, index) => setTimeout(() => activityService.listLaps(id), (5 + index) * 60)
)
//data.Item.json
});
}
and the streams function :
function getStreams(req) {
const idS = [
5567017025, 5566531480
];
const stravaClient = StravaClientService.getClient(access_token);
const activityService = StravaActivityService(stravaClient);
var params = {
TableName: "run-id",
Key: {
"id": "15428785",
}
};
console.log("cool streams")
docClient.get(params, async function (err, data) {
if (err) {
console.log("Error", err);
} else {
idS.map((id, index) => setTimeout(() => activityService.streamActivity(id), (5 + index) * 60))
console.log("got the streams")
}
});
}
in your getStream and getLaps function return promises instead of other object/Stuff like
async function getStream(){
return new Promise(async (resolve, reject){
//Do something
//where you want to return something just call resolve function like
resolve()
//if you want some output of getStream() just pass it to resolve function
//const result = 'I'm result'
resolve(result)
})
}
do same thing with the laps function and in your router call them with await keyword
I am trying to export the value with instrument variable. however data is returning as [object Promise] than object. How can I assign module variable with the final result rather than the promise object.
var instruments = {
data: async () => {
return new Promise((resolve, reject) => {
/// Respond after retrieving the data
resolve({result : "...." }
);
}
}
var symbols = async () => {
const res = await instruments.data();
return res;
}
module.exports.instrument = symbols().then((data) => {
console.log('data');
return data;
}).catch((e) => {
console.log('error');
return {}
});
It looks like you want a singleton cache. Here is a basic implementation
cache.js
let data = {}
module.exports = {
getData: () => {
return data
},
setData: newData => {
data = newData
return
},
}
No need for async here. I would separate this code with the code that retrieves data.
fetchData.js
const cache = require('./cache')
const fetchData = () => {} // fetch data code here
fetchData().then(data => {
cache.setData(data)
})
try this
var instruments = {
data: () => {
return new Promise((resolve, reject) => {
/// Respond after retrieving the data
resolve({result : "...." });
}
}
var symbols = async () => {
const res = await instruments.data();
return res;
}
module.exports.instrument = symbols;
then import instrument method to call and then call
const instrument = require("./filepath");
instrument().then((data) => {
console.log('data');
}).catch((e) => {
console.log(e);
});
If your async function instruments.data() called, it'll await return Promise.
just append await at return for your expected result.
var instruments = {
data: async () => {
return await new Promise((resolve, reject) => {
// Respond after retrieving the data
resolve({result : "...." });
}
}
or remove async. it's same as above.
var instruments = {
data: () => {
return new Promise((resolve, reject) => {
// Respond after retrieving the data
resolve({result : "...." });
}
}
My function is
exports.downloadFromBucket = function(fileKey) {
const localPath = `${process.cwd()}/data/${fileKey}`
return new Promise((resolve, reject) => {
const localFile = fs.createWriteStream(localPath)
const awsStream = s3.getObject({
Bucket: process.env.UPLOAD_BUCKET,
Key: fileKey
})
.createReadStream()
.on('error', (err) => {
logger.info('Error downloading file', err)
return reject(err)
})
.on('finish', () => {
logger.info('Completed downloading')
return resolve(localPath)
})
.pipe(localFile)
})
}
How would I go about writing a unit test for this using mocha and sinon?
This may not be the prettiest solution but assuming you want to mock s3 and fs and test the on('error') and on('finish') behavior:
You could use a custom s3 mocking class, stub the original s3 and fs with sinon and trigger the events you want to test.
// Custom S3 Mocking Library
class S3MockLibrary {
constructor() {
this.events = {};
}
getObject(options) {
return this;
}
createReadStream() {
return this;
}
on(event, func) {
this.events[event] = func;
return this;
}
pipe(file) {
return this;
}
emit(event, err) {
this.events[event](err);
}
}
Test on('finish')
it('should verify', async () => {
const s3Mock = new S3MockLibrary();
const fsStub = sinon.stub(fs, 'createWriteStream').returns('success');
const s3Stub = sinon.stub(s3, 'getObject').returns(s3Mock);
// Emit the finish event async
setTimeout(() => {
s3Mock.emit('finish');
}, 0);
const result = await downloadFromBucket('test');
fsStub.restore();
s3Stub.restore();
sinon.assert.calledOnce(fsStub);
sinon.assert.calledOnce(s3Stub);
assert.equal(result, `${process.cwd()}/data/test`);
});
Test on('error)
it('should fail', async () => {
const s3Mock = new S3MockLibrary();
const fsStub = sinon.stub(fs, 'createWriteStream').returns('success');
const s3Stub = sinon.stub(s3, 'getObject').returns(s3Mock);
setTimeout(() => {
s3Mock.emit('error', 'testError');
}, 0);
let error;
await downloadFromBucket('test').catch((err) => {
error = err;
});
fsStub.restore();
s3Stub.restore();
sinon.assert.calledOnce(fsStub);
sinon.assert.calledOnce(s3Stub);
assert.equal(error, 'testError');
});
I have a class that loads indexedDB. Before methods in the class can access it, I need to have indexedDB loaded beforehand. Currently I'm using an init() method prior to any other method that does not have this.db initialized.
I'm looking for a cleaner way to implement what I have, which definitely isn't DRY. Essentially every method is currently implemented with the same code pattern below.
Problem points are:
The requirement of another method init() in order to properly
handle the intialization of indexedDB.
The if (!this.db) { segment that ends up repeating itself later.
export default class Persist {
constructor(storage) {
if (storage) {
this.storage = storage;
}
else {
throw new Error('A storage object must be passed to new Persist()');
}
}
// needed to ensure that indexedDB is initialized before other methods can access it.
init () {
// initialize indexedDB:
const DBOpenRequest = this.storage.open('db', 1);
DBOpenRequest.onupgradeneeded = () => {
const db = DBOpenRequest.result;
db.createObjectStore('db', { keyPath: 'id', autoIncrement: true });
};
return new Promise((resolve, reject) => {
DBOpenRequest.onerror = event => {
reject(event);
};
DBOpenRequest.onsuccess = event => {
console.log(`IndexedDB successfully opened: ${event.target.result}`);
resolve(event.result);
this.db = DBOpenRequest.result;
};
});
}
toStorage(session) {
if (!this.db) {
return this.init().then(() => {
const db = this.db;
const tx = db.transaction('db', 'readwrite');
const store = tx.objectStore('db');
const putData = store.put(session.toJS());
return new Promise((resolve, reject) => {
putData.onsuccess = () => {
resolve(putData.result);
};
putData.onerror = () => {
reject(putData.error);
};
});
});
}
// basically a repeat of above
const db = this.db;
const tx = db.transaction('db', 'readwrite');
const store = tx.objectStore('db');
const putData = store.put(session.toJS());
return new Promise((resolve, reject) => {
putData.onsuccess = () => {
resolve(putData.result);
};
putData.onerror = () => {
reject(putData.error);
};
});
}
indexedDB provides asynchronous functions. indexedDB.open is an asynchronous function. It looks like you are trying to work with indexedDB in a non-asynchronous manner. Instead of storing the IDBDatabase variable as a property of the instance of your class, just return it as the resolve value and manage it external to the class.
function connect(name, version) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onupgradeneeded = myUpgradeHandlerFunction;
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
request.onblocked = () => { console.log('blocked'); };
});
}
function doStuffWithConn(conn, value) {
return new Promise((resolve, reject) => {
const tx = conn.transaction(...);
const store = tx.objectStore(...);
const request = store.put(value);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function putValue(value) {
let conn;
try {
conn = await connect(...);
await doStuffWithConn(conn, value);
} catch(exception) {
console.error(exception);
} finally {
if(conn)
conn.close();
}
}