bluebird - promise chain return unexpected "true" value - javascript

I'm writing an atom-shell app using ReactJs. Therefore I wrote 2 simple node.js-modules. The first fetches an XML api and returns an array of objects. The second is a simple datastore, which should save the fetched dataset. Both modules are async utilizing bluebird.
Now I have the following scenario:
var Promise = require('bluebird');
store.get()
.tap(print) //#1: []
.then(function (data) {
if (data.length) {
return Promise.resolve(data); //#6: ? also true in the second run ?
} else {
return xmlApi.get()
.tap(print) //#2: [...]
.then(store.insert) // <-- this returns an array with inserted indices
.tap(print) //#3: [...]
-then(store.write) // <-- this returns all stored objects as array
.tap(print) //#4: true <-- ? this is, what I doesn't understand... ?
}
})
.tap(print) //#5: true
.then(function (data) {
//Under normal circumstances, I would set ReactJs state from data array...
})
.catch(handleError)
.done();
My problem is to understand, why beyond step #3 everything resolves to true instead of arrays with values? When testing the libraries in pure node, everything is fine. But also in the second run, when the store holds data, the promise resolves to true...
UPDATE:
store.write
var write = Promise.promisify(require('fs').writeFile)
Store.prototype.write = function () {
return write(this.filename, JSON.stringify(this.data))
.bind(this)
.then(function () {
return Promise.resolve(this.data);
});
};

Related

Question about asynchronous JavaScript with Promise

Here I have a function that takes an array of string that contains the user names of github accounts. And this function is going to return an array of user data after resolving. There should be one fetch request per user. and requests shouldn’t wait for each other. So that the data arrives as soon as possible. If there’s no such user, the function should return null in the resulting array.
An example for the input would be ["iliakan", "remy", "no.such.users"], and the expected returned promise after resolving would give us [null, Object, Object], Object being the data that contained info about a user.
Here is my attempt to solve this question.
function getUsers(names) {
return new Promise(resolve => {
const array = [];
const url = "https://api.github.com/users/";
const requests = names.map(name => {
const endpoint = `${url}${name}`;
return fetch(endpoint);
});
Promise.all(requests).then(reponses => {
reponses.forEach(response => {
if (response.status === 200) {
response.json().then(data => {
array.push(data);
});
} else {
array.push(null);
}
});
resolve(array);
});
});
}
It does work, i.e. returning an array [null, Object, Object]. And I thought it fulfilled the requirements I stated above. However, after looking at it closely, I felt like I couldn't fully make sense of it.
My question is, look at where we resolve this array, it resolved immediately after the forEach loop. One thing I don't understand is, why does it contain all three items when some of the items are pushed into it asynchronously after the json() is finished. what I mean is, in the case where response.status === 200, the array is pushed with the data resolved from json(), and I would assume this json() operation should take some time. Since we didn't resolve the array after json() operation is finished, how come we still ended up with all data resolved from json()?
Promise.all(requests).then(reponses => {
reponses.forEach(response => {
if (response.status === 200) {
response.json().then(data => {
array.push(data); <--- this should take some time
});
} else {
array.push(null);
}
});
resolve(array); <--- resolve the array immediately after the `forEach` loop
});
});
It looks to me like the array we get should only have one null in it since at the time it is revolved, the .json() should not be finished
You're right, the result is pushed later into the array.
Try to execute this:
const test = await getUsers(['Guerric-P']);
console.log(test.length);
You'll notice it displays 0. Before the result is pushed into the array, its length is 0. You probably think it works because you click on the array in the console, after the result has arrived.
You should do something like this:
function getUsers(names) {
const array = [];
const url = "https://api.github.com/users/";
const requests = names.map(name => {
const endpoint = `${url}${name}`;
return fetch(endpoint);
});
return Promise.all(requests).then(responses => Promise.all(responses.map(x => x.status === 200 ? x.json() : null)));
};
You should avoid using the Promise constructor directly. Here, we don't need to use it at all.
const url = "https://api.github.com/users/";
const getUsers = names =>
Promise.all(names.map(name =>
fetch(url + name).then(response =>
response.status === 200 ? response.json() : null)));
getUsers(["iliakan", "remy", "no.such.users"]).then(console.log);
The Promise constructor should only be used when you're creating new kinds of asynchronous tasks. In this case, you don't need to use the Promise constructor because fetch already returns a promise.
You also don't need to maintain an array and push to it because Promise.all resolves to an array. Finally, you don't need to map over the result of Promise.all. You can transform the promises returned by fetch.
The thing is that because json() operation is really quick, especially if response data is small in size it just has the time to execute. Second of all as objects in JavaScript passed by reference and not by value and Array is a object in JavaScript, independently of execution time it'll still push that data to the array even after it was resolved.

Array of objects in javascript not returning as expected

I have a function which returns a list of objects in Javascript, and I'm calling this function from another and attempting to use some of the values from it, but whenever I try to access said values, they come back undefined.
This is my function which generates the list - the idea is that it creates a sqlite3 database if it does not exist, and returns an array containing every event.
function listAllEvents() {
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('schedule.db');
const selectionArray = [];
db.serialize(() => {
db.run(`
CREATE TABLE IF NOT EXISTS todo (
name text,
date text,
id text primary key
)
`);
db.all('SELECT * FROM todo ORDER BY date', [], (err, rows) => {
if (err) {
throw err;
}
rows.forEach((row) => {
selectionArray.push(row);
});
});
});
return selectionArray;
}
I call this function from another, but when I try to access values from the array, they don't seem to be working and I can't quite figure it out.
function displayUpcomingEvents() {
const events = listAllEvents();
// console.log(events); <-- This line here! In the console, it correctly states the length of the array
// console.log(events.length) <-- This line, however, returns 0. Why?
// console.log(events[0]) <-- This doesn't work either, it just returns "undefined".
for (let i = 0; i < events.length; i += 1) {
$('#upcomingEvents').after('<li>asdf</li>');
}
}
For example, if I were to create two events in the database, through the console,
events is an Array(2) with indices
- 0: {name: "my_event", date: "2019-06-04", id: "c017c392d446d4b2"}
- 1: {name: "my_event_2", date: "2019-06-04", id: "6d655ac8dd02e3fd"},
events.length returns 0,
and events[0] returns undefined.
Why is this, and what can I do to fix it?
The possible reason why this is happening, is because of the async nature of JS, that means all the console.log statements are getting executed before the successful execution of the listAllEvents() function,
So my suggestion is to try using the promises, and perform all the actions mentioned after the listAllEvents() function only when that function returns a promise.
You can also try making the function async and using await to wait for its successful execution. (Much Smarter Choice will be using async)
Link to ASYNC Functions and Usage
Link to Promises
Also you can check the validity of answer by doing console.log(row) where you are pushing rows to the array. You will observer that the console.log(row) will be executed at the last, after printing events and other log statements.
The problem is that your function is returning the variable before a value is set. The db.serialize function will run asynchronously (outside the normal flow of the program) and the return statement will run immediately after. One thing you can do is use async/await in conjunction with Promise. In this case the the variable results will wait for the promise to be resolved before continuing to the next line.
async function listAllEvents() {
const selectionArray = [];
let promise = new Promise( function (resolve, reject) {
db.serialize(() => {
db.run(
CREATE TABLE IF NOT EXISTS todo (
name text,
date text,
id text primary key
)
);
db.all('SELECT * FROM todo ORDER BY date', [], (err, rows) => {
if (err) {
// add code here to reject promise
throw err;
}
rows.forEach((row) => {
selectionArray.push(row);
});
resolve(selectionArray);// resolve the promise
});
});
});
let results = await promise;
return results;
};
async function displayUpcomingEvents() {
const events = await listAllEvents();
// console.log(events); <-- This line here! In the console, it correctly states the length of the array
// console.log(events.length) <-- This line, however, returns 0. Why?
// console.log(events[0]) <-- This doesn't work either, it just returns "undefined".
for (let i = 0; i < events.length; i += 1) {
$('#upcomingEvents').after('<li>asdf</li>');
}
}
Note here that the displayUpcomingEvents function will also need to be async or you cannot use the await keyword.
Additional reading for Promise keyword MDN: Promise
Additional reading for Async/Await MDN: Asyn/Await

Promise nested deep map

i am trying to use Promise to make something easy, but it appear to be a real nightmare with promises. I think i am missing something with it.
I would Like to :
Fetch some articles in database
Look into each article found and iterate over article.authors Array.
Fetch each author in dataBase (including author.images) for each article
Send back to client the articles List from step One but updated with article.authors.images
I tryed severals ways using Map / Each / spread / Reduce / _.clone / _cloneDeep
But nothing works as expected
Any help would be appreciate
return Promise.bind({})
.then(function find_article(){
return Article.find().sort(req.params.sort).skip(req.params.page).limit(req.params.limit).populateAll()
}).then(function(articles){
dataBack = articles
var articlesPromise = Promise.map(articles,function(article){
console.log('------------');
var AuthorsPromise = Promise.map(article.authors,function(author){
return User.findOne(author.id).populateAll().then(function(){
})
})
return Promise.all(AuthorsPromise).then(function(data){
console.log('*******************************');
console.log(data);
return data
})
})
return Promise.all(articlesPromise).then(function(allArticles){
console.log('++++++++++++++++++++++++++++++');
console.log(allArticles);
})
})
.then(function(WhatIsInThere){
console.log('somethinAfter');
console.log(WhatIsInThere);
})
I got Something like this, but is still doesnt work i am still missing the point of the .all()
This is not trivial task, you need to chain promises and most probably use function like Promise.all() or jQuery.when()
Your code should look like this
// function that fetch from database and returns promise
function getArticleFromDatabase(articleId) {
return new Promise();
}
// function that fetch from database and returns promise
function getAuthorFromDatabase(authorId) {
return new Promise();
}
var articleIds = [1, 2, 3]; // lets have some array with article ids
// then turn it into array of promises
var articlePromises = articleIds.map(function(articleId) {
var articlePromise = getArticleFromDatabase(articleId);
// return the promise
return articlePromise.then(function(articleData) {
// here we have available complete article data
var articleAuthors = articleData.authors; // supose it's array of author ids
// lets turn it into author data promises
var authorsPromises = articleAuthors.map(function(author) {
return getAuthorFromDatabase(author.id);
});
// return new Promise, so our first promise of article data
// will return promise of article data with authors data
return Promise.all(authorsPromises)
.then(function(fullfilledAuthorsData) {
// fill in authors data
articleData.authors = fullfilledAuthorsData;
// return complete article data with authors data
return articleData;
});
});
});
Promise.all(articlePromises).then(function(fullfilledArticleData) {
// here you have available complete article data from all articles
// fullfilledActicledata is array mapped from initial array of ids
// so fullfilledActicleData[0] has data for articleIds[0],
// fullfilledActicleData[1] has data for articleIds[1] etc.
// You can use the fullfilledArticleData freely.
});
Based on your code
// this method obviously returns Promise already
var articlesPromise = Article
.find()
.sort(req.params.sort)
.skip(req.params.page)
.limit(req.params.limit)
.populateAll();
// attach callback via .then()
articlesPromise
.then(function(articles) {
// here we have fullfilled articles data already
var articlesWithAuthorsPromises = articles.map(function(article) {
var authorsPromises = article.authors.map(function(author) {
return User.findOne(author.id).populateAll();
});
return Promise.all(authorsPromises)
.then(function(fullfilledAuthors) {
article.authors = fullfilledAuthors;
return article;
})
})
// return new Promise
return Promise.all(articlesWithAuthorsPromises)
})
// attach another callback via .then()
.then(function(fullData) {
console.log(fullData);
})
// you should also listen for errors
.catch(errorCallback)

testing using Babel,Bluebird Promises and Mocha runs tests in wrong order

I am a bit confused and I am also a newcomer to promises.
My problem is that the beforeEach function runs in random order (how is this possible?) so that the delete query is not run first. Also the order changes if I put console.log messages in the promise (??). What am I doing wrong here.
Note that the database (neo4j in this case) returns 200 OK in case there is a constrain violation that is why I am reading the result and then rejecting.
I have this test (using chai-as-promised):
beforeEach(function() {
return neodb.query('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r',{}) //clear the db first
.then(neodb.query(query,{props:[{username:'arisAlexis'}]}))
.then(neodb.query(query,{props:[{username:'marySilva'}]}))
.then(neodb.query(query,{props:[{username:'silviaBurakof'}]}));
});
it('create a username conflict',function() {
return User.create([{username:'arisAlexis'}]).should.be.rejected;
})
User.create is a static method that just runs this :
return db.query(query,{props:props});
so it returns a promise that is created here:
exports.query=function(query,params) {
return new Promise(function(resolve,reject) {
request.postAsync({
uri: dbUrl,
json: {statements: [{statement: query, parameters: params}]}
}).then(function(result) {
if (result[1].errors.length > 0) {
reject(new NeoError(result[1].errors[0].message,result[1].errors[0].code));
}
resolve(result[1].results);
});
});}
I am running mocha with babel-node.
I am also using promisify on request module and I am rejecting with a custom error (am I using it correctly ? In the documentation they throw the error).
You are executing all query functions at the same time, instead of waiting for the previous one to end:
.then(neodb.query(...)) // this executes `neodb.query()` right away
// and uses its return value as the callback
// function for the `.then()`.
Here's how to fix that:
beforeEach(function() {
return neodb.query('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r',{}).then(function() {
return neodb.query(query,{props:[{username:'arisAlexis'}]});
}).then(function() {
return neodb.query(query,{props:[{username:'marySilva'}]});
}).then(function() {
return neodb.query(query,{props:[{username:'silviaBurakof'}]});
});
});

Can Bluebird Promise work with redis in node.js?

Here's my original code to fetch a user php session which is stored in redis:
var session_obj;
var key = socket.request.headers.cookie.session
session.get('PHPREDIS_SESSION:'+key,function(err,data){
if( err )
{
return console.error(err);
}
if ( !data === false)
{
session_obj = PHPUnserialize.unserializeSession(data);
}
/* ... other functions ... */
})
I would like to rewrite the code with Promise, but I can't get the data returned:
Promise.resolve(session.get('PHPREDIS_SESSION:'+key)).then(function(data){
return data;
}).then(function(session){
console.log(session); // this returns true, but not the session data
session_obj = PHPUnserialize.unserializeSession(session);
}).catch(function(err){
console.log(err);
})
The session returned just boolean true instead of the session data. The original redis get function has two arguments. I assumed the promise just returned the first argument which was err in the original. So I tried
Promise.resolve(session.get('PHPREDIS_SESSION:'+key)).then(function(err,data){
console.log(data) // return undefined
})
but it wasn't working either.
Does anyone know if redis could work with Promise?
You were trying to use Promise.resolve wrong, it expects a Promise and session.get by default doesn't return a Promise. You first need to promisify it. (or promisifyAll)
session.getAsync = Promise.promisify(session.get);
// OR
Promise.promisifyAll(session); //=> `session.getAsync` automatically created
// OR
Promise.promisifyAll(redis); //=> Recursively promisify all functions on entire redis
Then use the new function which returns the promise like you would use a promise, you don't even need to wrap it with Promise.resolve, just this:
session.get('PHPREDIS_SESSION:' + key).
then(function(data) {
// do something with data
}).
catch(function(err) {
// handle error with err
});

Categories

Resources