Passing messages through Promises - google-chrome-extension

I'm trying to make an extension where :
background creates a new blank tab in the browser
then update the new tab with an url
then open a message port to the new tab
then pass a message to content script to retrieve element on the new tab's page
then content script respond to background with the title
I'm trying to do that with Promises, because after above example, I want to make it with an array of urls, so with a for loop. So I need to do it synchronously, one url after one url.
Here's my code :
background.js
function createNewTab() {
return new Promise(function (resolve, reject) {
var newTabId = 0;
chrome.tabs.create({
url: "about:newtab"
}, function(newTab) {
resolve(newTab);
});
});
}
function updateTab(newTab) {
console.log("updateTab try with tab " + newTab.id);
return new Promise(function (resolve, reject) {
chrome.tabs.update(newTab.id, {
url: "https://forum.openstreetmap.fr/viewtopic.php?f=3&t=6811&start=0"
}, function (tab) {
if (tab.url == "https://forum.openstreetmap.fr/viewtopic.php?f=3&t=6811&start=0")
resolve(["updated", tab])
else
reject(["failed", null])
});
});
}
function connectTab(newTab) {
console.log("connectTab try with tab " + newTab.id);
return new Promise(function (resolve, reject) {
var port = chrome.tabs.connect(newTab.id, {
name: "scrap"
});
resolve(port);
});
}
function passMsgToTab(port) {
return new Promise(function (resolve, reject) {
port.postMessage({
type: "title"
});
resolve("passed");
reject("failed");
});
}
createNewTab()
.then(function(newTab) {
console.log("createNewTab newTab : " + newTab.id);
return updateTab(newTab)
})
.then(function(responseTab) {
var response = responseTab[0];
var newTab = responseTab[1];
console.log("updated ? " + response + " | tab : " + newTab.id);
if (response == "updated")
return connectTab(newTab)
})
.then(function (port) {
console.log("connectTab port : " + port.name);
return passMsgToTab(port)
})
.then(function (response) {
console.log("message passed ? " + response);
})
And content_script.js :
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name == "scrap");
port.onMessage.addListener(function (msg) {
console.log("recieved msg : " + msg.type);
if (msg.type == "title") {
var title = $("h3.first a").text();
port.postMessage({
title: title
});
}
});
});
So the first 2 steps (createTab & updateTab) success. But message passing fails. I retrieve the response from connectTab but as I'm not putting resolve in a callback function, I think it doesn't work. I can reach port.name from then() but what can I do with this port ? And Where can I put the resolve ?
I think this is the same problem with port.message.

Related

Return a value from function inside promise

I am trying to return the array shiftInfo from the function csData() within a promise.
function crewsense(){
var request = CS.find({});
request
.then(result => {
var created = result[0].created,
currentTime = moment(),
diff = (currentTime - created);
if(diff < 84600000){
console.log("Current Token is Valid");
var access_token = result[0].access_token;
console.log('Obtaining Crewsense Shift Data');
return access_token
}else{
console.log("Current Token is invalid. Updating Token");
csToken();
}
}).then(access_token => {
csData(access_token) //I am trying to get this function to return async data.
}).then(shiftInfo => { //I want to use the data here.
})
Here is the csData function:
function csData(csKey) {
const dayURL = {
method: 'get',
url: 'https://api.crewsense.com/v1/schedule?start='+today+'%2007:30:00&end='+tomorrow+'%2007:30:00',
headers:{
Authorization: csKey,
}
}
const request = axios(dayURL)
request
.then(result => {
var shiftInfo = [];
var thisShift = [];
var onDuty = result.data.days[moment().format("YYYY-MM-DD")].assignments;
thisShift.push(result.data.days[moment().format("YYYY-MM-DD")].day_color);
var persons = [];
var i = 0;
for(var i=0; i<onDuty.length; i++){
let station = onDuty[i].name
for(var x=0; x<onDuty[i].shifts.length; x++){
var person = {
name: onDuty[i].shifts[x].user.name,
position: onDuty[i].shifts[x].qualifiers[0].name,
station: station
}
persons.push(person);
}
}
shiftInfo = [{thisShift}, {persons}];
// console.log(shiftInfo)
return shiftInfo
})
.catch(error => console.error('csData error:', error))
}
I have attempted assigning var shiftInfo = csData(access_token) w/o success and several other ways to call the csData function. I have attempted reading other like problems on here and I have just ended up confused. If someone can point me in the right direction or please point out the fix I might be able to get it to click in my head.
I appreciate everyone's time.
Thanks!
Whatever you return inside a then, will be passed to the next then callback. If you return a Promise, the result of the promise will be sent to the next then callback:
new Promise((resolve) => {
// We resolve to the value we want
resolve("yay");
}).then((value) => {
// In the first then, value will be "yay"
console.log("First then:", value);
// Then we return a new value "yay x2"
return value + " x2";
}).then((value) => {
// In this second then, we received "yay x2"
console.log("Second then:", value);
// Then we return a promise that will resolve to "yay x2 again"
return new Promise((resolve) => {
setTimeout(() => {
resolve(value + " again");
}, 1000);
});
}).then((value) => {
// After a second (when the returned Promise is resolved) we get the new value "yay x2 again"
console.log("Third then:", value);
// And now we return a Promise that will reject
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("wtf"));
}, 1000);
});
}).catch((error) => {
// This catch on the whole promise chain will catch any promise rejected
console.log(error.toString());
});
So simply csData must return the promise is creating, and you need to return that promise to the then callback you want:
[...]
}).then(access_token => {
return csData(access_token) //I am trying to get this function to return async data.
}).then(shiftInfo => { //I want to use the data here.
console.log(shiftInfo);
}).catch((err) => {
// Whatever...
});
function csData(csKey) {
[...]
return request.then(result => {
[...]
}
Because you are returning a promise, I recommend you to add the catch outside csData and add it to the promise chain you have before.

How to run same promises one after another NodeJs

I am trying to solve the following problem.
Consider the following case. I need to check if an array of servers is alive. Or to be more specific I need to find the first working server from the provided list, I need to do this one by one.
For example if the first server doesn't work, check another and another ...
As far as NodeJS is asynchronous I cannot do this in a for loop. So I tried to implement something similar to recursion, it looks ugly and doesn't work, but I've tried)
static findWorkingServer(servers, payload) {
return NetworkUtils.getMyPublicIP()
.then((ip) => {
return new Promise(function (resolve, reject) {
let currentIndex = -1;
if (servers.length > 0) {
let currentServer;
let serverCheckCallback = function (result) {
if (result) {
resolve({working: currentServer, payload: payload});
}
else {
if (currentIndex < servers.length-1) {
currentIndex++;
currentServer = servers[currentIndex];
NetworkUtils.checkIfServerWorking(currentServer, ip)
.then(serverCheckCallback);
}
else {
reject(new Error("No working servers found"))
}
}
};
serverCheckCallback(false);
}
else {
resolve(new Error("No servers provided"));
}
})
});
}
static checkIfServerWorking(credentials, publicIp) {
return new Promise(function (resolve, reject) {
if(credentials) {
request({
url: credentials.url,
agentClass: agentClass,
agentOptions: {
// Agent credentials
}
})
.then(res => {
// Do some stuff with resposne
resolve(someCondition);
})
.catch(err => {
resolve(false);
});
}else {
resolve(false);
}
});
}
Please help to get the desired result, maybe it is possible to run requests synchronously.
Could be done with await/async:
let servers = ["test0.com","test1.com","test2.com","test3.com","test4.com"]
class ServerTest {
static async checkServer(name) {
if (name === "test3.com")
return true //returns promise that resolves with true
else
return false //returns promise that resolves with false
}
}
(async()=>{ //IIFE (await can only be used in async functions)
let targetServer
for (i in servers) {
if (await ServerTest.checkServer(servers[i]) === true) {
targetServer = servers[i]
break
}
}
console.log("Found a working server: " + targetServer)
})()

Herarchy query using sequelize / nodejs

I am trying to load a hierarchy in my database. I have a column with parentId in my table so every row can have a parent. But I am having problems using recursion and promises.
function read (options) {
return serviceItemAttributeModel.findOne({
id: options.id,
id_organization: options.idOrganization
})
.then((attribute) => {
if (attribute) {
return loadChildren(attribute, attribute);
} else {
return attribute;
}
});
}
function loadChildren (root, attribute) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
if (!attributes) {
return root;
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each);
});
}
});
}
So, I call read that calls loadChildren to recursively try to load all entities (by looking children of an entity) and I get an undefined value. Any ideas?
I am also getting an error on console: a promise was created in a handler but was not returned from it.
EDIT:
Came up if this solution after Nosyara help. thanks!:
function read (options) {
return serviceItemAttributeModel.findOne({
where: {
id: options.attributeId,
id_organization: options.idOrganization
}
})
.then((attribute) => {
if (!attribute) {
return new Promise(function (resolve, reject) {
resolve(attribute);
});
} else {
return new Promise(function (resolve, reject) {
attribute.queryCount = 1;
resolve(attribute);
})
.then((attribute) => loadChildren(attribute, attribute));
}
});
}
function loadChildren (root, attribute) {
return new Promise(function (resolve, reject) {
return serviceItemAttributeModel.findAll({
where: {
id_parent: attribute.id
}
})
.then((attributes) => {
attributes.length = attributes.length || 0;
root.queryCount = root.queryCount - 1 + attributes.length;
if (root.queryCount === 0) {
resolve(root);
} else if (root.queryCount > 10) {
let error = new Error('Service attribute hierarchy cant have more then 10 levels');
error.statusCode = 500;
reject(error);
} else {
attribute.serviceItemAttributes = [];
attributes.forEach(function (each) {
attribute.serviceItemAttributes.push(each);
return loadChildren(root, each).then(() => {
resolve(root);
});
});
}
});
});
}
You messing up with async calls and returns. You can convert both function to async, and pass through result structure to be updated. Example:
function read(...) {
return new Promise(function (accept, reject) {
// You code goes here, but instead of return
accept(resultFromAsyncFunction);
});
}
// ...
read(...).then(function(resultData) { ... });
Here is example of Promise recursion.

Node.js promises and bookshelf.js

I'm new into Promises.
I use Bookshelf.js as ORM.
I fetch a number of webpages, get person info (about actors) from those pages and add them into my database if they don't exist.
But there's a problem, even though console.log(name) returns actor names in the right order, my query checks for only one actor, the latest one, which is 9.
What's wrong here?
var entities = require("entities");
var request = require('request');
var cheerio = require('cheerio');
// create promisified version of request()
function requestPromise(options) {
return new Promise(function (resolve, reject) {
request(options, function (err, resp, body) {
if (err) return reject(err);
resolve(body);
});
});
}
var person = require('./models').person;
app.get('/fetch', function (req, res) {
var promises = [];
var headers = {
'User-Agent': req.headers['user-agent'],
'Content-Type': 'application/json; charset=utf-8'
};
for (var i = 1; i < 10; i++) {
promises.push(requestPromise({url: "http://www.example.com/person/" + i + "/personname.html", headers: headers}));
}
Promise.all(promises).then(function (data) {
// iterate through all the data here
for (var i = 0; i < data.length; i++) {
if ($ = cheerio.load(data[i])) {
var links = $("#container");
var name = links.find('span[itemprop="name"]').html(); // name
if (name == null) {
console.log("null name returned, do nothing");
} else {
name = entities.decodeHTML(name);
console.log(name); // returns names in the right order
// does this person exist in the database?
person.where('id', i).fetch().then(function (result) {
if (result) {
console.log(i + "exists");
} else {
console.log(i + " doesn't exist");
// returns "9 doesn't exists" 9 times instead of
// checking each ID individually, why?
}
});
}
} else {
console.log("can't open");
}
}
}, function (err) {
// error occurred here
console.log(err);
});
});
EDIT #2
Now the order is broken and my ID's aren't the same with the site's I fetch data from. I see ID's like 11 and 13 even though I iterate from 1 to 5 and it seems to overrule something since it adds duplicate entries.
Here's what I'm trying to do in a nutshell. "Visit these urls in order and add the data you fetch (e.g. names) in the same order (id1 = name1; id2 = name2, etc) to the database".
app.get('/fetch', function (req, res) {
var promises = [];
var headers = {
'User-Agent': req.headers['user-agent'],
'Content-Type': 'application/json; charset=utf-8'
};
for (var i = 1; i < 5; i++) {
promises.push(requestPromise({url: "http://example.com/person/ + i + "/personname.html", headers: headers}));
}
Promise.all(promises).then(function (data) {
// iterate through all the data here
data.forEach(function (item, i) {
var $ = cheerio.load(item);
var name = $("#container span[itemprop='name']").text();
if (!name) {
console.log("null name returned, do nothing");
} else {
// name exists
person.where('id', i).fetch({require: true}).then(function (p) {
console.log(i + " exists");
}).catch(function () {
console.log(i + " does not exist");
new person({id: i, name: name}).save(null, {method: 'insert'}).then(function () {
console.log("success" + i);
});
});
}
}, function (err) {
// error occurred here
console.log(err);
});
});
});
When you run your code through jshint, you will see a warning that says
Don't make functions within a loop.
In this piece of code the callback inside then does not run in sync with the enclosing for loop. It runs whenever the database has fetched your result.
person.where('id', i).fetch().then(function (result) {
if (result) {
console.log(i + "exists");
} else {
console.log(i + " doesn't exist");
}
});
Therefore, when that callback runs eventually, the loop has long finished. Your callback function holds a reference to the loop counter i - which, by now, has the value 9.
It's better to use a function that accepts a parameter than to refer to a loop counter.
Luckily node makes this easy, you can use the forEach array function:
data.forEach(function (item, i) {
var $ = cheerio.load(item);
var name = $("#container span[itemprop='name']").text();
if (!name) {
console.log("null name returned, do nothing");
} else {
console.log("successfully scraped name: " + name);
person.where('id', i).fetch({require: true}).then(function (p) {
console.log(i + " exists");
}).catch(function () {
console.log(i + " does not exist");
});
}
});
Note that you can make Bookshelf.js throw instead of silently passing over non-existing records with {require: true}.
More generally speaking, I don't see a real connection between scraping the name from a website and retrieving a model from the database. These two things should probably be done in separate functions that each return an individual promise for the respective thing. That way, requests to the database can run in parallel with requests to the web server.
It looks like you need a closure for person.where('id', i).fetch().
also use node-fetch instead of hand rolling request-promise.

NODE.JS function callback to render page

I have a function getthem() that checks a mongo db for listings and returns name,streamurl for it.
I pass those as var stream to the renderme that renders the /dashboard page.
My problem is that i get the console.log("END OF FIND:"+JSON.stringify(stream))
to show my test input, but nothing goes to the render.
im using ejs to render. How can i get the result passed to the page ?
router.get('/dashboard', function (req, res) {
var foo = getthem();
function getthem() {
var group = "tesint";
console.log('geting for group : ' + group);
var mdlListings = require('../models/listings.js');
var myresult = "tet";
mdlListings.find({}, "name streamurl", function (err, data) {
if (err) {
console.error(err);
return;
}
if (data === null) {
console.log("No results");
return;
}
var stream = { };
data.forEach(function (streams) {
console.log("Got " + streams.name + " " + streams.streamurl);
stream[streams.name] = streams.streamurl;
// stream += 'name: '+streams.name+'},{streamurl: '+streams.streamurl;
// console.log("stram arry "+stream[streams.name] )
console.log("END OF FIND:"+JSON.stringify(stream))
}, renderme(stream));
// console.log("Result:", votes);
//var myresult = Object.keys(stream).map(function (name) {
// return { name: name, url: stream[name] };
//})
console.log("before return stream "+stream);
return stream;
});
}
function renderme(resa) {
console.log("RESA"+JSON.stringify(resa))
var resa = JSON.stringify(resa);
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
listing: resa
}
)
}
You're passing the result of renderme(stream) as a second argument to forEach(). renderme(stream) is then evaluated immediately before your forEach() callback is called, when stream is still an empty object. My guess is you want something like this:
data.forEach(function (streams) {
console.log("Got " + streams.name + " " + streams.streamurl);
stream[streams.name] = streams.streamurl;
console.log("END OF FIND:"+JSON.stringify(stream))
});
renderme(stream);
Actually i figure that why would i pass the function as i could just do whatever i need to directly in there.
That worked perfectly, thanks for the tip.
data.forEach(function (streams) {
console.log("Got " + streams.name + " " + streams.streamurl);
stream[streams.name] = streams.streamurl;
});
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
listing: data
}
)

Resources