How do I promisify node-adodb? - javascript

I'm trying to promisify node-adodb using bluebirdjs.
I've tried this:
import Promise from 'bluebird'
import ADODB from 'node-adodb'
const db = ADODB.open(`...`)
const dbQuery = db.query(`...`)
const dbQueryOn = Promise.promisify(dbQuery.on, { context: dbQuery })
dbQueryOn('done').then(data => {
console.log('data =', data)
}).catch(err => {
console.log('err =', err)
})
The data is returned, but it comes via the .catch() not the .then() method.
How do I get node-adodb working with promises..?

I'm not familiar with node-adodb, but from its documentation it seems it uses a non-conventional way of returning errors and results (using event-like emitters).
Bluebird's promisify requires the regular Node.js callback convention (first argument represents errors, second argument represents the "result" value), so you can't use it in this situation.
But you can wrap it yourself:
const db = ADODB.open(`...`);
const runQuery = query => {
return new Promise((resolve, reject) => {
db.query(query)
.on('done', resolve)
.on('fail', reject);
});
}
// Usage:
runQuery(`...`).then(data => {
console.log('data =', data)
}).catch(err => {
console.log('err =', err)
})

Related

Iterate over array of queries and append results to object in JavaScript

I want to return results from two database queries in one object.
function route(start, end) {
return new Promise((resolve, reject) => {
const queries = routeQuery(start, end);
var empty_obj = new Array();
for (i=0; i<queries.length; i++) {
query(queries[i], (err, res) => {
if (err) {
reject('query error', err);
console.log(err);
return;
} else {
empty_obj.push(res.rows);
}});
}
console.log(empty_obj);
resolve({coords: empty_obj});
});
}
This is my code right now, the queries are working fine but for some reason, pushing each result into an empty array does not work. When I console log that empty object, it stays empty. The goal is to resolve the promise with the generated object containing the two query results. I'm using node-postgres for the queries.
Output of res is an object:
{
command: 'SELECT',
rowCount: 18,
oid: null,
rows: [
{ ...
I suggest you turn your query function into a Promise so that you can use Promise.all:
// turn the callback-style asynchronous function into a `Promise`
function queryAsPromise(arg) {
return new Promise((resolve, reject) => {
query(arg, (err, res) => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve(res);
});
});
}
Then, you could do the following in your route function:
function route(start, end) {
const queries = routeQuery(start, end);
// use `Promise.all` to resolve with
// an array of results from queries
return Promise.all(
queries.map(query => queryAsPromise(query))
)
// use `Array.reduce` w/ destructing assignment
// to combine results from queries into a single array
.then(results => results.reduce(
(acc, item) => [...acc, ...item.rows],
[]
))
// return an object with the `coords` property
// that contains the final array
.then(coords => {
return { coords };
});
}
route(1, 10)
.then(result => {
// { coords: [...] }
})
.catch(error => {
// handle errors appropriately
console.error(error);
});
References:
Promise.all - MDN
Array.reduce - MDN
Destructing assignment - MDN
Hope this helps.
The issue you currently face is due to the fact that:
resolve({coords: empty_obj});
Is not inside the callback. So the promise resolves before the query callbacks are called and the rows are pushed to empty_obj. You could move this into the query callback in the following manner:
empty_obj.push(res.rows); // already present
if (empty_obj.length == queries.length) resolve({coords: empty_obj});
This would resolve the promises when all rows are pushed, but leaves you with another issue. Callbacks might not be called in order. Meaning that the resulting order might not match the queries order.
The easiest way to solve this issue is to convert each individual callback to a promise. Then use Promise.all to wait until all promises are resolved. The resulting array will have the data in the same order.
function route(start, end)
const toPromise = queryText => new Promise((resolve, reject) => {
query(queryText, (error, response) => error ? reject(error) : resolve(response));
});
return Promise.all(routeQuery(start, end).map(toPromise))
.then(responses => ({coords: responses.map(response => response.rows)}))
.catch(error => {
console.error(error);
throw error;
});
}

How to mock an object with a function that has a callback?

I have this simple method that i'm trying to test.
function isS3Healthy(s3, logger = console) {
return new Promise((resolve, reject) => {
s3.listObjects({}, (err, data) => {
if (err) {
logger.error(err)
return resolve(['s3', false])
}
return resolve(['s3', true])
})
})
}
My test keeps timing out and it has to do with my mocked object isn't right. What does this mock object need to look like in order to test my method properly?
describe.only('#isS3Healthy', () => {
let s3
before(() => {
s3 = {
listObjects: ({}, (err, data) => {
return Promise.resolve(['s3', true])
})
}
})
it('should return that it is healthy', () => {
return isS3Healthy(s3)
.then(result => {
const [name, status] = result
expect(name).to.equal('s3')
expect(status).to.be.true
})
.catch(err => {
console.log('err = ', err)
})
})
})
Based on the code you've provided, it looks like listObjects is a function that accepts an object and a callback, and at some point calls that callback, so probably the simplest way to mock it would be like this:
listObjects: (_, c) => c()
The function you're testing only seems to care whether listObjects is passing an error to the callback or not, so this should seemingly be sufficient.
Side note: you have return resolve in two places in your isS3Healthy function, but using return here almost certainly serves no purpose. To resolve a promise, just call resolve(...) without returning it.

Changing callback into a promise chain

I was trying to start using promise chaining (was using callbacks so far), and I wanted to edit this code:
Account.findById(req.user._id,
(err, acc) => {
if (err) console.log(err);
var r = req.body;
acc.fullName = r.fullName;
acc.displayname = r.username;
acc.city = r.city;
acc.province = r.province;
acc.postalCode = r.postalCode;
acc.phone = r.phone;
acc.ageGroup = r.ageGroup;
acc.education = r.education;
acc.lookingForWork = r.lookingForWork;
acc.employmentStatus = r.employmentStatus;
acc.workingWithEOESC = r.workingWithEOESC;
acc.resume = r.resume;
acc.mainWorkExp = r.mainWorkExp;
acc.save();
res.redirect('/seeker');
})
This is what I tried to do:
Account.findById(req.user._id)
.then((err, acc) => {
if (err) console.log(err);
var r = req.body;
acc.fullName = r.fullName;
acc.displayname = r.username;
acc.city = r.city;
acc.province = r.province;
acc.postalCode = r.postalCode;
acc.phone = r.phone;
acc.ageGroup = r.ageGroup;
acc.education = r.education;
acc.lookingForWork = r.lookingForWork;
acc.employmentStatus = r.employmentStatus;
acc.workingWithEOESC = r.workingWithEOESC;
acc.resume = r.resume;
acc.mainWorkExp = r.mainWorkExp;
acc.save();
})
.catch(e => console.log(e))
.then((acc) => {
console.log(acc);
res.redirect('/seeker');
})
});
But the promise version throws a TypeError: Cannot set property 'fullName' of undefined error.
The changes are not being saved and console loging the acc results in undefined. Forgot to add that in the post
I'm just learning promises. What am I missing? The inside code is almost exactly the same.
.then functions in promises can take maxiumum two argument which must be both functions, the first function is when the promise is fullfilled, while the second function is when the promise is rejected, alternatively you can pass in only one function to .then and use .catch to handle any kind of error or a rejected promise
var f1 = acc => console.log(acc); // logs out the acc object;
var f2 = err => console.log(err); // logs out error while executing the promise
.then(f1,f2); // when you do this there is no need for a catch block
// or
.then( acc => {
console.log(acc) // logs out the acc object
}).catch( err => console.log(err) ) //logs out the error
// if you need to handle another value
.then( acc => {
console.log(acc);
return acc.save(); //lets say acc.save() returns an object
}).then( acc => console.log(acc) ); // the value of acc.save() is passed down to the next `.then` block
Callback-based API's have a common convention of using the first argument of the callback function to indicate failure. Promises require no such convention, because they have built-in means of handling failures, so you need to just operate on the first argument, not the second. The second argument will be undefined, resulting in the error you're seeing.
Most of the time when you're translating callback-based code to promise-based code, you want to use this pattern as your basic guide:
// Callback-based:
asyncFn((err, result) => {
if (err) {
// handle failure
} else {
// handle success
}
});
// Promise-based equivalent:
asyncFnPromise()
.then((result) => {
// handle success
}, (err) => {
// handle failure
});
// Alternative promised-based:
asyncFnPromise()
.then((result) => {
// handle success.
// Note that unlike the above, any errors thrown here will trigger
// the `catch` handler below, in addition to actual asyncFnPromise
// failures.
})
.catch((err) => {
// handle failure
});
then is just called on success, therefore there is definetly no error:
then((acc) => {
It is happening because the function 'findById' probably not returning 'promise' and just returning some response.You need to create a 'promise object' in findById function and return it.
findById (){
let promise = new Promise((resolve, reject) => {
Suppose results is yield from some async task, so when
//wanted results occured
resolve(value);
//unwanted result occured
reject(new Error('Something happened!'));
return promise;
}
findById.then(response => {
console.log(response);
}, error => {
console.log(error);
});

reading file with ES6 promises

let arr = [];
function getData(fileName, type) {
return fs.readFile(fileName,'utf8', (err, data) => {
if (err) throw err;
return new Promise(function(resolve, reject) {
for (let i = 0; i < data.length; i++) {
arr.push(data[i]);
}
resolve();
});
});
}
getData('./file.txt', 'sample').then((data) => {
console.log(data);
});
When I use above code and run it in command line using nodejs I get following error.
getData('./file.txt', 'sample').then((data) => {
^
TypeError: Cannot read property 'then' of undefined
How can I solve this?
You'll want to wrap the entire fs.readFile invocation inside a new Promise, and then reject or resolve the promise depending on the callback result:
function getData(fileName, type) {
return new Promise(function(resolve, reject){
fs.readFile(fileName, type, (err, data) => {
err ? reject(err) : resolve(data);
});
});
}
[UPDATE] As of Node.js v10, you can optionally use the built-in Promise implementations of the fs module by using fs.promises.<API>. In the case of our readFile example, we would update our solution to use fs.promises like this:
function getData(fileName, type) {
return fs.promises.readFile(fileName, {encoding: type});
}
Nobody told about util.promisify so I'm going to post, however old the question is.
Why are you having this message?
getData('./file.txt', 'sample').then((data) => {
^
TypeError: Cannot read property 'then' of undefined
getData is a wrapper for fs.readFile file here. fs.readfile is not a thenable (it does not implement a then function). It is built on the other pattern, the callback pattern. The most well-known thenable are Promises, and that's what you want to get from readFile I believe. A little reminder: Mozilla - Promises
So what you can do is either implement it yourself as did #hackerrdave or I would suggest using promisify: this function is a built-in function of Node.js which was implemented to transform the callback-based function into promised based. You will find it here: Node.js Documentation for util.promisfy
It basically does the same as #hackerrdave but it's more robust and built-in node util.
Here's how to use it:
const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile)
readFile("path/to/myfile").then(file => console.log(file))
Here is a one-liner as of node 10.2.0:
(async () => console.log(String(await require('fs').promises.readFile('./file.txt'))))();
Yes, it is now out of the box.
As of Node 12+, you can use the fs.promises API.
See an example below:
const { readFile } = require('fs').promises
readFile('./file.txt', { encoding: 'utf8' })
.then((data) => console.log(data))
.catch((error) => console.error(error));
Using async/await
const { readFile } = require('fs').promises
async function readFile(filePath) {
try {
const data = await readFile(filePath, { encoding: 'utf8' })
console.log(data)
} catch (error) {
console.error(error.message)
}
}
readFile('./file.txt')
const getData = (fileName, type) =>
new Promise((resolve, reject) =>
fs.readFile(fileName, type, (err, data) => {
//if has error reject, otherwise resolve
return err ? reject(err) : resolve(data);
})
);
getData('./file.txt', 'utf8')
.then(data => console.log('Data: ', data))
.catch(error => console.log('Error: ', error));
Update for current node As of node 10.0.0 you can now use fs.promises:
const fs = require('fs')
(async function(){
var fileContents = await fs.promises.readFile(FILENAME)
var data = JSON.parse(fileContents)
})()

Get Knex.js transactions working with ES7 async/await

I'm trying to couple ES7's async/await with knex.js transactions.
Although I can easily play around with non-transactional code, I'm struggling to get transactions working properly using the aforementioned async/await structure.
I'm using this module to simulate async/await
Here's what I currently have:
Non-transactional version:
works fine but is not transactional
app.js
// assume `db` is a knex instance
app.post("/user", async((req, res) => {
const data = {
idUser: 1,
name: "FooBar"
}
try {
const result = await(user.insert(db, data));
res.json(result);
} catch (err) {
res.status(500).json(err);
}
}));
user.js
insert: async (function(db, data) {
// there's no need for this extra call but I'm including it
// to see example of deeper call stacks if this is answered
const idUser = await(this.insertData(db, data));
return {
idUser: idUser
}
}),
insertData: async(function(db, data) {
// if any of the following 2 fails I should be rolling back
const id = await(this.setId(db, idCustomer, data));
const idCustomer = await(this.setData(db, id, data));
return {
idCustomer: idCustomer
}
}),
// DB Functions (wrapped in Promises)
setId: function(db, data) {
return new Promise(function (resolve, reject) {
db.insert(data)
.into("ids")
.then((result) => resolve(result)
.catch((err) => reject(err));
});
},
setData: function(db, id, data) {
data.id = id;
return new Promise(function (resolve, reject) {
db.insert(data)
.into("customers")
.then((result) => resolve(result)
.catch((err) => reject(err));
});
}
Attempt to make it transactional
user.js
// Start transaction from this call
insert: async (function(db, data) {
const trx = await(knex.transaction());
const idCustomer = await(user.insertData(trx, data));
return {
idCustomer: idCustomer
}
}),
it seems that await(knex.transaction()) returns this error:
[TypeError: container is not a function]
I couldn't find a solid answer for this anywhere (with rollbacks and commits) so here's my solution.
First you need to "Promisify" the knex.transaction function. There are libraries for this, but for a quick example I did this:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
This example creates a blog post and a comment, and rolls back both if there's an error with either.
const trx = await promisify(db.transaction);
try {
const postId = await trx('blog_posts')
.insert({ title, body })
.returning('id'); // returns an array of ids
const commentId = await trx('comments')
.insert({ post_id: postId[0], message })
.returning('id');
await trx.commit();
} catch (e) {
await trx.rollback();
}
Here is a way to write transactions in async / await.
It is working fine for MySQL.
const trx = await db.transaction();
try {
const catIds = await trx('catalogues').insert({name: 'Old Books'});
const bookIds = await trx('books').insert({catId: catIds[0], title: 'Canterbury Tales' });
await trx.commit();
} catch (error) {
await trx.rollback(error);
}
Async/await is based around promises, so it looks like you'd just need to wrap all the knex methods to return "promise compatible" objects.
Here is a description on how you can convert arbitrary functions to work with promises, so they can work with async/await:
Trying to understand how promisification works with BlueBird
Essentially you want to do this:
var transaction = knex.transaction;
knex.transaction = function(callback){ return knex.transaction(callback); }
This is because "async/await requires the either a function with a single callback argument, or a promise", whereas knex.transaction looks like this:
function transaction(container, config) {
return client.transaction(container, config);
}
Alternatively, you can create a new async function and use it like this:
async function transaction() {
return new Promise(function(resolve, reject){
knex.transaction(function(error, result){
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Start transaction from this call
insert: async (function(db, data) {
const trx = await(transaction());
const idCustomer = await(person.insertData(trx, authUser, data));
return {
idCustomer: idCustomer
}
})
This may be useful too: Knex Transaction with Promises
(Also note, I'm not familiar with knex's API, so not sure what the params are passed to knex.transaction, the above ones are just for example).
For those who come in 2019.
After I updated Knex to version 0.16.5. sf77's answer doesn't work anymore due to the change in Knex's transaction function:
transaction(container, config) {
const trx = this.client.transaction(container, config);
trx.userParams = this.userParams;
return trx;
}
Solution
Keep sf77's promisify function:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
Update trx
from
const trx = await promisify(db.transaction);
to
const trx = await promisify(db.transaction.bind(db));
I think I have found a more elegant solution to the problem.
Borrowing from the knex Transaction docs, I will contrast their promise-style with the async/await-style that worked for me.
Promise Style
var Promise = require('bluebird');
// Using trx as a transaction object:
knex.transaction(function(trx) {
var books = [
{title: 'Canterbury Tales'},
{title: 'Moby Dick'},
{title: 'Hamlet'}
];
knex.insert({name: 'Old Books'}, 'id')
.into('catalogues')
.transacting(trx)
.then(function(ids) {
return Promise.map(books, function(book) {
book.catalogue_id = ids[0];
// Some validation could take place here.
return knex.insert(book).into('books').transacting(trx);
});
})
.then(trx.commit)
.catch(trx.rollback);
})
.then(function(inserts) {
console.log(inserts.length + ' new books saved.');
})
.catch(function(error) {
// If we get here, that means that neither the 'Old Books' catalogues insert,
// nor any of the books inserts will have taken place.
console.error(error);
});
async/await style
var Promise = require('bluebird'); // import Promise.map()
// assuming knex.transaction() is being called within an async function
const inserts = await knex.transaction(async function(trx) {
var books = [
{title: 'Canterbury Tales'},
{title: 'Moby Dick'},
{title: 'Hamlet'}
];
const ids = await knex.insert({name: 'Old Books'}, 'id')
.into('catalogues')
.transacting(trx);
const inserts = await Promise.map(books, function(book) {
book.catalogue_id = ids[0];
// Some validation could take place here.
return knex.insert(book).into('books').transacting(trx);
});
})
await trx.commit(inserts); // whatever gets passed to trx.commit() is what the knex.transaction() promise resolves to.
})
The docs state:
Throwing an error directly from the transaction handler function automatically rolls back the transaction, same as returning a rejected promise.
It seems that the transaction callback function is expected to return either nothing or a Promise. Declaring the callback as an async function means that it returns a Promise.
One advantage of this style is that you don't have to call the rollback manually. Returning a rejected Promise will trigger the rollback automatically.
Make sure to pass any results you want to use elsewhere to the final trx.commit() call.
I have tested this pattern in my own work and it works as expected.
Adding to sf77's excellent answer, I implemented this pattern in TypeScript for adding a new user where you need to do the following in 1 transaction:
creating a user record in the USER table
creating a login record in the LOGIN table
public async addUser(user: User, hash: string): Promise<User> {
//transform knex transaction such that can be used with async-await
const promisify = (fn: any) => new Promise((resolve, reject) => fn(resolve));
const trx: knex.Transaction = <knex.Transaction> await promisify(db.transaction);
try {
let users: User [] = await trx
.insert({
name: user.name,
email: user.email,
joined: new Date()})
.into(config.DB_TABLE_USER)
.returning("*")
await trx
.insert({
email: user.email,
hash
}).into(config.DB_TABLE_LOGIN)
.returning("email")
await trx.commit();
return Promise.resolve(users[0]);
}
catch(error) {
await trx.rollback;
return Promise.reject("Error adding user: " + error)
}
}

Categories