Twilio Method inside of promise does not work - javascript

Reading
https://www.twilio.com/blog/implementing-programmable-chat-php-laravel-vue-js
I try implement chat in my Laravel 8 / jQuery 3.5.1 / vue 2.6 app.
This docs has defined :
setupChannel(channel){
let vm = this;
return this.leaveCurrentChannel()
.then(function() {
return vm.initChannel(channel);
})
.then(function(_channel) {
return vm.joinChannel(_channel);
})
.then(this.initChannelEvents);
},
I want to extend joinChannel method, as I want to make checks if current logged user (laravel auth)
is already joined. I try to make it with promise and failes, as code inside of
vm.tc.messagingClient.getSubscribedUsers() is not run. I do
setupChannel(channel){
let vm = this;
return this.leaveCurrentChannel()
.then(function() {
return vm.initChannel(channel);
})
.then(function(_channel) {
let promise = new Promise((resolve, reject) => {
// debugger
vm.tc.messagingClient.getSubscribedUsers().then(function(users) {
// THESE CODE IS NOT RUN. If to uncomment console and debugging it is not triggered
// console.log('++ users::')
// console.log(users)
// debugger
for (let i = 0; i < users.length; i++) {
const user = users[i];
console.log('user.identity: ' + JSON.stringify(user.identity) );
// console.log('user: ' + JSON.stringify(user, null, 2) );
if( user.identity === vm.loggedUser.name ) {
resolve("result")
}
}
debugger // THESE CODE IS NOT RUN
resolve("error")
})
})
console.log('++ promise::')
console.log(promise) // I SEE this promise in pending state
promise
.then(
result => {
alert("result: " + result);
return _channel;
},
error => {
alert("error: " + error);
return vm.joinChannel(_channel);
}
)
// return vm.joinChannel(_channel);
})
.then(this.initChannelEvents);
If to run code
vm.tc.messagingClient.getSubscribedUsers().then(function(users)
...
inside of promise, it works ok and I got valid results.
What is wrong in my promise structure and how can I fix it?
MODIFIED BLOCK:
I try to follow your way with :
joinGeneralChannel() {
console.log('Attempting to join "general" chat channel...');
let vm = this;
if (this.tc.generalChannel == null) {
console.log('SETTING this.tc.messagingClient.createChannel')
vm.loadChannelList(vm.joinGeneralChannel);
}else {
// console.log('Found general channel:');
this.setupChannel(this.tc.generalChannel);
}
},
async setupChannel(channel) {
let vm = this
await this.leaveCurrentChannel()
const newChannel = await vm.initChannel(channel)
const subscribedUsers = vm.tc.messagingClient.getSubscribedUsers()
console.log('subscribedUsers::')
console.log(subscribedUsers)
let isUserJoined = false
for (let i = 0; i < subscribedUsers.length; i++) {
console.log('subscribedUsers[i] ' + JSON.stringify(subscribedUsers[i]) );
if( subscribedUsers[i].name === vm.loggedUser.name ) {
isUserJoined = true``
break
}
}
debugger
console.log('isUserJoined::')
console.log(isUserJoined)
But in the cosole of my browser I see :
Initialized channel General Channel
TeamChat.vue?e1c8:561 subscribedUsers::
TeamChat.vue?e1c8:562 PromiseĀ {<pending>}__proto__: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined
TeamChat.vue?e1c8:573 isUserJoined::
looks like method getSubscribedUsers is asynchronous ?
Thanks!

Probably your Promise fails, that's why then() will never execute. To extend joinChannel method you can do something like this with async/await and ES6 syntax:
async setupChannel(channel) {
let vm = this;
try {
await this.leaveCurrentChannel();
const newChannel = await vm.initChannel(channel);
const users = await vm.tc.messagingClient.getSubscribedUsers();
const isUserJoined = users.some(({ name }) => name === vm.loggedUser.name);
const joinedChannel = isUserJoined ? newChannel : vm.joinChannel(_channel);
return this.initChannelEvents(joinedChannel);
} catch(err) {
// if some of promises below will fail, here you'll see details
console.log('Issue details here:', err);
}
}

Related

forEach loop behaving strangely with called function values logged at end of the loop instead of during

EDIT: currently i think the problem with this is that forEach is not promise aware. https://zellwk.com/blog/async-await-in-loops/
I am trying to apply a node javascript translation function (ive put it at the end of the post because it is quite long) to loop over an array of values. However when i loop for some reason i'm only getting certain parts of my looped function to appear after the loop has completed: Allow me to make this more clear:
array = [["hello", "hello" ],
["my", "my", ],
["name", "name" ],
["is", "my" ],
["joe", "joe"]]
function process (item,index){
const translate = require('#vitalets/google-translate-api');
console.log('loopaction'); //this shows that the loop is executing
translate(item[0], {to: 'sp'}).then(result => {
console.log(result.text);
}).catch(err => {
console.error(err);
})
array.forEach(process); // Applying the function process to the array in a ForEach loop
from this i am getting
loopaction
loopaction
loopaction
loopaction
loopaction
hola
mi
nombre
es
joe
So it seems that the forEach loop is completing before the values are being allowed to be displayed. Which is something i really don't understand since the array values are being translated correctly and then logged out in the correct order. As if they had been stored in the memory for later. And then called at the end of the forEach loop order.
The translate function looks like this:
function translate(text, opts, gotopts) {
opts = opts || {};
gotopts = gotopts || {};
var e;
[opts.from, opts.to].forEach(function (lang) {
if (lang && !languages.isSupported(lang)) {
e = new Error();
e.code = 400;
e.message = 'The language \'' + lang + '\' is not supported';
}
});
if (e) {
return new Promise(function (resolve, reject) {
reject(e);
});
}
opts.from = opts.from || 'auto';
opts.to = opts.to || 'en';
opts.tld = opts.tld || 'com';
opts.from = languages.getCode(opts.from);
opts.to = languages.getCode(opts.to);
var url = 'https://translate.google.' + opts.tld;
return got(url, gotopts).then(function (res) {
var data = {
'rpcids': 'MkEWBc',
'f.sid': extract('FdrFJe', res),
'bl': extract('cfb2h', res),
'hl': 'en-US',
'soc-app': 1,
'soc-platform': 1,
'soc-device': 1,
'_reqid': Math.floor(1000 + (Math.random() * 9000)),
'rt': 'c'
};
return data;
}).then(function (data) {
url = url + '/_/TranslateWebserverUi/data/batchexecute?' + querystring.stringify(data);
gotopts.body = 'f.req=' + encodeURIComponent(JSON.stringify([[['MkEWBc', JSON.stringify([[text, opts.from, opts.to, true], [null]]), null, 'generic']]])) + '&';
gotopts.headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
return got.post(url, gotopts).then(function (res) {
var json = res.body.slice(6);
var length = '';
var result = {
text: '',
pronunciation: '',
from: {
language: {
didYouMean: false,
iso: ''
},
text: {
autoCorrected: false,
value: '',
didYouMean: false
}
},
raw: ''
};
try {
length = /^\d+/.exec(json)[0];
json = JSON.parse(json.slice(length.length, parseInt(length, 10) + length.length));
json = JSON.parse(json[0][2]);
result.raw = json;
} catch (e) {
return result;
}
if (json[1][0][0][5] === undefined) {
// translation not found, could be a hyperlink?
result.text = json[1][0][0][0];
} else {
json[1][0][0][5].forEach(function (obj) {
if (obj[0]) {
result.text += obj[0];
}
});
}
result.pronunciation = json[1][0][0][1];
// From language
if (json[0] && json[0][1] && json[0][1][1]) {
result.from.language.didYouMean = true;
result.from.language.iso = json[0][1][1][0];
} else if (json[1][3] === 'auto') {
result.from.language.iso = json[2];
} else {
result.from.language.iso = json[1][3];
}
// Did you mean & autocorrect
if (json[0] && json[0][1] && json[0][1][0]) {
var str = json[0][1][0][0][1];
str = str.replace(/<b>(<i>)?/g, '[');
str = str.replace(/(<\/i>)?<\/b>/g, ']');
result.from.text.value = str;
if (json[0][1][0][2] === 1) {
result.from.text.autoCorrected = true;
} else {
result.from.text.didYouMean = true;
}
}
return result;
}).catch(function (err) {
err.message += `\nUrl: ${url}`;
if (err.statusCode !== undefined && err.statusCode !== 200) {
err.code = 'BAD_REQUEST';
} else {
err.code = 'BAD_NETWORK';
}
throw err;
});
});
}
I realise there is a promise format and the problem im having may have to do with the asychronisity of the function and how the long the promise is taking to get resolved. I cant seem to figure out why the promise is not resolving or displaying after my forEach function is completely looped yet it seems to be saved correctly and in the correct order. Very odd.
Any ideas about what is it about the function translate() that is making this happen? Is there anyway i can rewrite my function process () to make sure that the translate functions resolved promise and the .then() in function process () is fully executed before moving on?
You are correct, you are using promises, so translate() will run asynchronously (in the background) while the rest of your code is executing. That is why you go through all the foreach() before the translate function returns, and therefore you get that output.
However, there is also a problem using a forEach loop in an async function or a promise block. The callback function is not being awaited. Therefore, the promise chain is broken, resulting in the unexpected behavior.
Don't use forEach loop in a promise or async function. Instead, use a for loop to iterate through the items of the array:
To avoid these problems, change the forEach loop to a For loop and use async and await like this:
async function process (item,index){
const translate = require('#vitalets/google-translate-api');
console.log('loopaction'); //this shows that the loop is executing
await translate(item[0], {to: 'sp'})
.then(result => {
console.log(result.text);
})
.catch(err => {
console.error(err);
})
}
async function main() {
array = [["hello", "hello" ],
["my", "my" ],
["name", "name" ],
["is", "my" ],
["joe", "joe"]]
for (let i = 0; i < array.length; i++) {
await process(array[i], i);
}
}
main()
await makes the function wait until the promise is resolved.
NOTE: You tried to create a timeout with object.sleep(), this doesn't exist in javascript, use setTimeout() instead, refer to: Sleep() in Javascript

Cannot await for sqlite3.Database.get() function completion in Node.js

I'm struggling with some basic async/await problem in node.js using node-sqlite3.
My objective is to select some value from SQLite DB, check it for some condition and take some actions in case the condition is met. Here's the code:
const sqlite3 = require('sqlite3').verbose();
main();
async function main() {
let ordersDb = await createDbConnection('./ProcessedOrders.db');
var orderProcessed = await orderAlreadyProcessed(ordersDb, "555");
console.log("orderProcessed = " + orderProcessed);
if (!orderProcessed) {
console.log("So condition is met!");
}
}
async function orderAlreadyProcessed(ordersDb, orderNumberStr) {
console.log('starting orderAlreadyProcessed function'); //DEBUG
var result;
var query = 'select count(SoldOrderNumber) as "recsCount" from ProcessedSoldOrders where SoldOrderNumber = ?;';
await ordersDb.get(query
,[orderNumberStr]
,(err, row) => {
console.log('Row with count = ' + row); //DEBUG
console.log('row.recsCount = ' + row.recsCount); //DEBUG
result = typeof row !== 'undefined' && row.recsCount > 0;
});
console.log('Returning ' + result); //DEBUG
return result;
}
async function createDbConnection(dbFileName) {
let db = new sqlite3.Database(dbFileName, (err) => {
if (err) {
console.log(err.message);
}
});
return db;
}
But what I get is code executing further, not awaiting for Database.get() method at all! As a result, here's what I see printing in console:
starting orderAlreadyProcessed function
Returning undefined
orderProcessed = undefined
So IF condition met!
Row with count = [object Object]
row.recsCount = 1
As we can see, we return from orderAlreadyProcessed too early with return value = 'undefined'. So condition is met, actions taken, and only then Database.get() returns. But if it was properly awaited, condition would not be met.
How can I make it await for result value?
Since you want to use async/await, and the node-sqlite3 (sqlite3) library does not support the Promise API, you need to use the node-sqlite (sqlite) library, which is a wrapper over sqlite3 and adds support for the Promise API. Then, your code will look something like this:
const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
async function main() {
try {
sqlite3.verbose();
const ordersDb = await createDbConnection('./ProcessedOrders.db');
const orderProcessed = await orderAlreadyProcessed(ordersDb, "555");
console.log("orderProcessed = " + orderProcessed);
if (!orderProcessed) {
console.log("So condition is met!");
}
} catch (error) {
console.error(error);
}
}
async function orderAlreadyProcessed(ordersDb, orderNumberStr) {
try {
console.log('Starting orderAlreadyProcessed function');
const query = 'SELECT COUNT(SoldOrderNumber) as `recsCount` from ProcessedSoldOrders where SoldOrderNumber = ?;'
const row = await ordersDb.get(query, [orderNumberStr]);
console.log('Row with count =', row);
console.log('row.recsCount =', row.recsCount);
const result = typeof row !== 'undefined' && row.recsCount > 0;
console.log('Returning ' + result);
return result;
} catch (error) {
console.error(error);
throw error;
}
}
function createDbConnection(filename) {
return open({
filename,
driver: sqlite3.Database
});
}
main();
I specifically did not remove your console.log and other parts of the code so as not to confuse the original logic of your program.
If we don't to use another library
then we can return a new Promise function & use await, as below:
Note: Below has example for INSERT/run, instead of SELECT/get, but promise/await works same
const sqlite3 = require("sqlite3").verbose();
let db;
db = new sqlite3.Database('./Chinook.db');
function insert() {
return new Promise((resolve, reject) => { // return new Promise here <---
const userId = uuid4();
let sql = `INSERT INTO Users(id) VALUES (?)`; // INSERT <----
let params = [userId];
return db.run(sql, params, function (err, res) { // .run <----
if (err) {
console.error("DB Error: Insert failed: ", err.message);
return reject(err.message);
}
return resolve("done");
});
});
}
let result = await insert(); // now await works fine <------
res.send({ result });

Issue with mongoose.save never returning inside of promise

Update !!
I fixed my initial issue with the help of Dacre Denny answer below however when writing tests for my code it turned out that the changes were not being saved before the server responded therefor the company collection in my test database was empty, I fixed this issue with the following code
Companies.find({ company_name: company.company_name }).then(found => {
if (found.length !== 0) {
return res.status(400).json({ error: "Company already exists" });
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
console.log(finalCompany);
var userPromises = [];
for (var x = 0; x < userForms.length; x++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
return Promise.all(userPromises).then(responses => {
for (var x in responses) {
if (!responses[x].errors) {
finalCompany.addUser(responses[x]._id);
} else {
res.status(400).json(responses[x]);
}
}
return finalCompany;
});
})
// I moved the save in here !!!
.then((finalCompany) => {
finalCompany.save().then(()=>{
res.status(200).json({signup:"Successful"});
})
},(err) => {
res.json({error: err});
});
});
Original Issue
I am trying to create a mongoose document to represent a company, this code saves the model in my db however it does not seem to be responding with a status code or reply to postman when I make a request
I've used a debugger to step through the code but I am very rusty on my JS and I am afraid I've gone into deep water with promises thats gone over my head.
router.post('/c_signup', auth.optional, (req, res, next) => {
const { body: { company } } = req;
var error_json = cbc(company);
if( error_json.errors.length > 0 ){
return res.status(422).json(error_json);
}
Companies.find({company_name: company.company_name})
.then((found) => {
if (found.length !== 0) {
return res.status(400).json({error: "Company already exists"});
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
var userPromises = [];
for (var x =0; x < userForms.length; x ++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
Promise.all(userPromises).then((responses) => {
for (var x in responses){
if (!responses[x].errors){
finalCompany.addUser(responses[x]._id);
}
else {
res.status(400).json(responses[x]);
}
}
console.log("h2");
finalCompany.save(function () {
console.log("h3");
return res.status(200);
});
})
});
return res.status(404);
});
This is the output from the debug but the execution is hanging here
h2
h3
There are a few issues here:
First, the save() function is asynchronous. You'll need to account for that by ensuring the promise that save() returns, is returned to the handler that it's is called in.
The same is true with the call to Promise.all() - you'll need to add that promise to the respective promise chain by returning that promise to the enclosing handler (see notes below).
Also, make sure the request handler returns a response either via res.json(), res.send(), etc, or by simply calling res.end(). That signals that the request has completed and should address the "hanging behaviour".
Although your code includes res.json(), there are many cases where it's not guaranteed to be called. In such cases, the hanging behaviour would result. One way to address this would be to add res.end() to the end of your promise chain as shown below:
Companies.find({ company_name: company.company_name }).then(found => {
if (found.length !== 0) {
return res.status(400).json({ error: "Company already exists" });
}
var userForms = company.users;
company.users = [];
const finalCompany = new Companies(company);
var userPromises = [];
for (var x = 0; x < userForms.length; x++) {
var user = userForms[x].user;
user.company = finalCompany._id;
userPromises.push(userCreation(user));
}
/* Add return, ensure that the enclosing then() only resolves
after "all promises" here have completed */
return Promise.all(userPromises).then(responses => {
for (var x in responses) {
if (!responses[x].errors) {
finalCompany.addUser(responses[x]._id);
} else {
res.status(400).json(responses[x]);
}
}
console.log("h2");
/* Add return, ensure that the enclosing then() only resolves
after the asnyc "save" has completed */
return finalCompany.save(function() {
console.log("h3");
return res.status(200);
});
});
})
.then(() => {
res.end();
},(err) => {
console.error("Error:",err);
res.end();
});

How to chain promises within nested for loops?

var verifyEmail = function (thisEmail){
return new Promise(
function (resolve, reject) {
quickemailverification.verify(thisEmail, function (err, response) {
// Print response object
console.log(response.body);
if (response.body["success"] == "true"){
var validity = response.body["result"];
if (validity == "valid"){
console.log("Email Valid!");
resolve(validity);
} else {
console.log("Email Invalid!")
resolve(validity);
}
} else {
var reason = new Error("API unsuccessful");
reject(reason);
}
});
}
);
};
var saveValidity = function (validity){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
var state = true;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
}
}
);
};
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
var snap = snapshot.val();
keys = Object.keys(snap);
for (var i = 0; i < 100; i++){
var emails = snap[keys[i]]["emails"];
if (emails){
for (var x = 0; x<emails.length; x++){
var thisEmail = emails[x]["email"];
var emailVerified = emails[x]["verified"];
if (emailVerified != true || emailVerified != false){
verifyEmail
.then(saveValidity)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error){
console.log(error.message);
});
}
}
}
}
});
Above is the code I put together. I'm not all too convinced that it will work. I'm new to promises, so I'm trying to understand how to do this right.
The verifyEmail function should take in the email address from the firebase query in the third chunk of the code. The saveValidity function should take on the validity response from verifyEmail.
But, what I'm also worried about the nested for loop I have in the firebase query block. I'm looping through each user to validate their emails, but each user sometimes also has multiple emails. I'm worried that it will loop on to the next user before finishing checking all the emails of the previous user.
I'm also not sure if I can pass data into the promise functions the way I did.
Could definitely use some help here. Really trying hard to understand how this works.
First, you need to fix saveValidity() to always resolve or reject the promise and to pass in the other variables key and thisEmail that it references:
const saveValidity = function (validity, key, thisEmail){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
let state = true;
admin.database().ref("/users_unverified/"+key+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error("Email ("+thisEmail+") verfication could not be saved" + error));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error(msg));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
}
}
);
};
Then, several changes are made to your main loop:
I assume we can run all the verifyEmail() calls in parallel since they don't appear to have anything to do with one another.
Change verifyEmail.then(...) to verifyEmail(thisEmail).then(...)` to actually call the function
Collect all the verifyEmail() promises in an array
Call Promise.all() on the array of promises to monitor when they are all done
Return value from .then() so we get the returned values in Promise.all()
rethrow in .catch() so promise stays rejected and will filter back to Promise.all(). You could eat errors here if you want to ignore them and continue with others.
Switch from var to let
Change from != to !== since it looks like your explicitly looking for a true or false value and don't want type casting.
Pass in the variables that saveValidity() needs.
Change logic when comparing emailVerified because what you had before was always true and thus probably not the right logic. I think what you want is to know when emailVerified is not yet set to true or to false which means you have to use &&, not ||.
Compare outer for loop with keys.length, not hard-coded value of 100.
And, here's the resulting code for the main nested for loop:
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
let snap = snapshot.val();
let keys = Object.keys(snap);
let promises = [];
for (let i = 0; i < keys.length; i++){
let key = keys[i];
let emails = snap[key]["emails"];
if (emails){
for (let x = 0; x < emails.length; x++) {
let currentKey = key;
let thisEmail = emails[x]["email"];
let emailVerified = emails[x]["verified"];
if (emailVerified !== true && emailVerified !== false){
promises.push(verifyEmail(thisEmail).then(validity => {
return saveValidity(validity, currentKey, thisEmail);
}).then(function (fulfilled) {
console.log(fulfilled);
return fulfilled; // after logging return value so it stays the resolved value
}).catch(function (error) {
console.log(error.message);
throw error; // rethrow so promise stays rejected
}));
}
}
}
}
return Promise.all(promises);
}).then(results => {
// all results done here
}).catch(err => {
// error here
});
If ES2017 is available in your case, you can just use the keywords await and async to do that directly. Following is an example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
And you can read more about async/await here.
If you want to do that without async/await to achieve better browser compatibility, you can use Babel to do the pre-compile.
If you really want a lightwight implementation, you can use a function named chainPromiseThunks, or chain for short. This chain function accepts an Array of Thunks of Promises, And returns a new Thunk of Promise, Following is an one-line-implementation of chain:
const chain = thunks => thunks.reduce((r, a) => () => r().then(a));
And here is a usage demo:
const echo = x =>
new Promise(function(resolve) {
return setTimeout((function() {
console.log(x);
return resolve(x);
}), 1000);
})
;
const pThunks = [1,2,3,4,5].map(i => () => echo(i));
chain(pThunks)();

How do I access <state> and <value> properties of a Promise? [duplicate]

I have a pure JavaScript Promise (built-in implementation or poly-fill):
var promise = new Promise(function (resolve, reject) { /* ... */ });
From the specification, a Promise can be one of:
'settled' and 'resolved'
'settled' and 'rejected'
'pending'
I have a use case where I wish to interrogate the Promise synchronously and determine:
is the Promise settled?
if so, is the Promise resolved?
I know that I can use #then() to schedule work to be performed asynchronously after the Promise changes state. I am NOT asking how to do this.
This question is specifically about synchronous interrogation of a Promise's state. How can I achieve this?
No such synchronous inspection API exists for native JavaScript promises. It is impossible to do this with native promises. The specification does not specify such a method.
Userland libraries can do this, and if you're targeting a specific engine (like v8) and have access to platform code (that is, you can write code in core) then you can use specific tools (like private symbols) to achieve this. That's super specific though and not in userland.
Nope, no sync API, but here's my version of the async promiseState (with help from #Matthijs):
function promiseState(p) {
const t = {};
return Promise.race([p, t])
.then(v => (v === t)? "pending" : "fulfilled", () => "rejected");
}
var a = Promise.resolve();
var b = Promise.reject();
var c = new Promise(() => {});
promiseState(a).then(state => console.log(state)); // fulfilled
promiseState(b).then(state => console.log(state)); // rejected
promiseState(c).then(state => console.log(state)); // pending
promise-status-async does the trick. It is async but it does not use then to wait the promise to be resolved.
const {promiseStatus} = require('promise-status-async');
// ...
if (await promiseStatus(promise) === 'pending') {
const idle = new Promise(function(resolve) {
// can do some IDLE job meanwhile
});
return idle;
}
You can make a race with Promise.resolve
It's not synchronous but happens now
function promiseState(p, isPending, isResolved, isRejected) {
Promise.race([p, Promise.resolve('a value that p should not return')]).then(function(value) {
if (value == 'a value that p should not return') {
(typeof(isPending) === 'function') && isPending();
}else {
(typeof(isResolved) === 'function') && isResolved(value);
}
}, function(reason) {
(typeof(isRejected) === 'function') && isRejected(reason);
});
}
A little script for testing and understand their meaning of asynchronously
var startTime = Date.now() - 100000;//padding trick "100001".slice(1) => 00001
function log(msg) {
console.log((""+(Date.now() - startTime)).slice(1) + ' ' + msg);
return msg;//for chaining promises
};
function prefix(pref) { return function (value) { log(pref + value); return value; };}
function delay(ms) {
return function (value) {
var startTime = Date.now();
while(Date.now() - startTime < ms) {}
return value;//for chaining promises
};
}
setTimeout(log, 0,'timeOut 0 ms');
setTimeout(log, 100,'timeOut 100 ms');
setTimeout(log, 200,'timeOut 200 ms');
var p1 = Promise.resolve('One');
var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "Two"); });
var p3 = Promise.reject("Three");
p3.catch(delay(200)).then(delay(100)).then(prefix('delayed L3 : '));
promiseState(p1, prefix('p1 Is Pending '), prefix('p1 Is Resolved '), prefix('p1 Is Rejected '));
promiseState(p2, prefix('p2 Is Pending '), prefix('p2 Is Resolved '), prefix('p2 Is Rejected '));
promiseState(p3, prefix('p3 Is Pending '), prefix('p3 Is Resolved '), prefix('p3 Is Rejected '));
p1.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
p2.then(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
p3.catch(prefix('Level 1 : ')).then(prefix('Level 2 : ')).then(prefix('Level 3 : '));
log('end of promises');
delay(100)();
log('end of script');
results with delay(0) (comment the while in delay)
00001 end of promises
00001 end of script
00001 Level 1 : One
00001 Level 1 : Three
00001 p1 Is Resolved One
00001 p2 Is Pending undefined
00001 p3 Is Rejected Three
00001 Level 2 : One
00001 Level 2 : Three
00001 delayed L3 : Three
00002 Level 3 : One
00002 Level 3 : Three
00006 timeOut 0 ms
00100 timeOut 100 ms
00100 Level 1 : Two
00100 Level 2 : Two
00101 Level 3 : Two
00189 timeOut 200 ms
and the results of this test with firefox(chrome keep the order)
00000 end of promises
00100 end of script
00300 Level 1 : One
00300 Level 1 : Three
00400 p1 Is Resolved One
00400 p2 Is Pending undefined
00400 p3 Is Rejected Three
00400 Level 2 : One
00400 Level 2 : Three
00400 delayed L3 : Three
00400 Level 3 : One
00400 Level 3 : Three
00406 timeOut 0 ms
00406 timeOut 100 ms
00406 timeOut 200 ms
00406 Level 1 : Two
00407 Level 2 : Two
00407 Level 3 : Two
promiseState make .race and .then : Level 2
in node, say undocumented internal process.binding('util').getPromiseDetails(promise)
> process.binding('util').getPromiseDetails(Promise.resolve({data: [1,2,3]}));
[ 1, { data: [ 1, 2, 3 ] } ]
> process.binding('util').getPromiseDetails(Promise.reject(new Error('no')));
[ 2, Error: no ]
> process.binding('util').getPromiseDetails(new Promise((resolve) => {}));
[ 0, <1 empty item> ]
You can use an (ugly) hack in Node.js until a native method is offered:
util = require('util');
var promise1 = new Promise (function (resolve) {
}
var promise2 = new Promise (function (resolve) {
resolve ('foo');
}
state1 = util.inspect (promise1);
state2 = util.inspect (promise2);
if (state1 === 'Promise { <pending> }') {
console.log('pending'); // pending
}
if (state2 === "Promise { 'foo' }") {
console.log ('foo') // foo
}
Updated: 2019
Bluebird.js offers this: http://bluebirdjs.com/docs/api/isfulfilled.html
var Promise = require("bluebird");
let p = Promise.resolve();
console.log(p.isFulfilled());
If you'd prefer to create your own wrapper, here is a nice blog about it.
Because JavaScript is single-threaded, it's hard to find a common enough use case to justify putting this in the spec. The best place to know if a promise is resolved is in .then(). Testing if a Promise is fullfilled would create a polling loop which is most likely the wrong direction.
async/await is a nice construct if you'd like to reason async code synchronously.
await this();
await that();
return 'success!';
Another useful call is Promise.all()
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
When I first reached for this answer, that is the use case I was looking for.
It's indeed quite annoying that this basic functionality is missing. If you're using node.js then I know of two workarounds, neither of 'em very pretty. Both snippets below implement the same API:
> Promise.getInfo( 42 ) // not a promise
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.resolve(42) ) // fulfilled
{ status: 'fulfilled', value: 42 }
> Promise.getInfo( Promise.reject(42) ) // rejected
{ status: 'rejected', value: 42 }
> Promise.getInfo( p = new Promise(() => {}) ) // unresolved
{ status: 'pending' }
> Promise.getInfo( Promise.resolve(p) ) // resolved but pending
{ status: 'pending' }
There doesn't seem to be any way to distinguish the last two promise states using either trick.
1. Use the V8 debug API
This is the same trick that util.inspect uses.
const Debug = require('vm').runInDebugContext('Debug');
Promise.getInfo = function( arg ) {
let mirror = Debug.MakeMirror( arg, true );
if( ! mirror.isPromise() )
return { status: 'fulfilled', value: arg };
let status = mirror.status();
if( status === 'pending' )
return { status };
if( status === 'resolved' ) // fix terminology fuck-up
status = 'fulfilled';
let value = mirror.promiseValue().value();
return { status, value };
};
2. Synchronously run microtasks
This avoids the debug API, but has some frightening semantics by causing all pending microtasks and process.nextTick callbacks to be run synchronously. It also has the side-effect of preventing the "unhandled promise rejection" error from ever being triggered for the inspected promise.
Promise.getInfo = function( arg ) {
const pending = {};
let status, value;
Promise.race([ arg, pending ]).then(
x => { status = 'fulfilled'; value = x; },
x => { status = 'rejected'; value = x; }
);
process._tickCallback(); // run microtasks right now
if( value === pending )
return { status: 'pending' };
return { status, value };
};
await usage to #jib's answer, with idiomatic prototyping.
Object.defineProperty(Promise.prototype, "state", {
get: function(){
const o = {};
return Promise.race([this, o]).then(
v => v === o ? "pending" : "resolved",
() => "rejected");
}
});
// usage: console.log(await <Your Promise>.state);
(async () => {
console.log(await Promise.resolve(2).state); // "resolved"
console.log(await Promise.reject(0).state); // "rejected"
console.log(await new Promise(()=>{}).state); // "pending"
})();
note that this async function execute "almost" immediately like synced function (or actually possibly be instantly).
You can wrap your promises in this way
function wrapPromise(promise) {
var value, error,
settled = false,
resolved = false,
rejected = false,
p = promise.then(function(v) {
value = v;
settled = true;
resolved = true;
return v;
}, function(err) {
error = err;
settled = true;
rejected = true;
throw err;
});
p.isSettled = function() {
return settled;
};
p.isResolved = function() {
return resolved;
};
p.isRejected = function() {
return rejected;
};
p.value = function() {
return value;
};
p.error = function() {
return error;
};
var pThen = p.then, pCatch = p.catch;
p.then = function(res, rej) {
return wrapPromise(pThen(res, rej));
};
p.catch = function(rej) {
return wrapPromise(pCatch(rej));
};
return p;
}
I looked through the solutions proposed to this question and could not see one that corresponds to a simple approach that I have used in Node.js.
I have defined a simple class PromiseMonitor, which takes a promise as the single parameter to its constructor, and has a string property .status which returns the standard string values corresponding to the promise status, "pending", "resolved" or "rejected", and four boolean properties .pending, .resolved, .rejected and .error. The property .error is set true only if .rejected is true and the reject callback was passed an Error object.
The class simply uses .then() on the promise to change the status of the PromiseMonitor when the promise is resolved or rejected. It does not interfere with any other use of the original promise. Here is the code:
class PromiseMonitor {
constructor(prm){
this._status = "pending";
this._pending = true;
this._resolved = false;
this._rejected = false;
this._error = false;
prm
.then( ()=>{
this._status = "resolved";
this._resolved = true;
this._pending = false;
}
, (err)=>{
this._status = "rejected";
this._pending = false;
this._rejected = true;
this._error = err instanceof Error ? true: false ;
}
);
}
get status(){ return this._status; };
get pending(){ return this._pending; };
get resolved(){ return this._resolved; };
get rejected(){ return this._rejected; };
get error(){ return this._error };
};
To monitor the status of a Promise, simply create an instance of PromiseMonitor, passing the promise in as a parameter, for example:
let promiseObject = functionThatReturnsAPromise();
let promiseMonitor = new PromiseMonitor( promiseObject );
Now you can syncrhonously check all the properties of promiseMonitor, which will track the status of the original promise. Here is a test script that demonstrates the three possible resolutions of a promise being monitored.
let ticks = 0;
let tickerID = setInterval( ()=>{++ticks; console.log(`..tick ${ticks}`)}, 1000);
async function run(){
console.log("Start");
let delay = prmDelay(2000);
let delayMonitor = new PromiseMonitor(delay);
// normal handling of delay promise
delay.then((result)=>( console.log("Normal resolution of delay using .then()") ) );
console.log("delay at start:\n", delay);
console.log("delayMonitor at start:\n", delayMonitor);
await delay;
console.log("delay finished:\n", delay);
console.log("delayMonitor finished:\n", delayMonitor);
console.log("\n\n TEST2: Rejection without an Error test ================================")
let rejDelay = prmDelay(3000, "reject");
let rejMonitor = new PromiseMonitor(rejDelay);
// normal handling of reject result on promise
rejDelay.then((result)=>( console.log("Normal resolution of rejDelay using .then will not happen") )
, (err)=>( console.log("Rejection of rejDelay handled using .then")));
console.log("rejDelay at start:\n", rejDelay);
console.log("rejMonitor at start:\n", rejMonitor);
await rejDelay.catch( (err)=>{ console.log( "Caught error using .catch on rejDelay" ); });
console.log("rejDelay finished:\n", rejDelay);
console.log("rejMonitor finished:\n", rejMonitor);
console.log("\n\n TEST3: Rejection with an Error test ================================")
let errMonitor ;
let errDelay;
try{
errDelay = prmDelay(1000, "error");
errMonitor = new PromiseMonitor(errDelay);
// normal handling of results of the original promise
errDelay.then(
(result)=>{
console.log("Normal expiry of errDelay");
console.log("Monitor Status is " + errMonitor.status )
}
, (err)=>{
console.log("** Rejection of errDelay handled using .then()");
console.log(" Monitor Status is " + errMonitor.status )
}
);
console.log("errDelay at start:\n", errDelay);
console.log("errMonitor at start:\n", errMonitor);
await errDelay;
console.log("**** This should never be run");
} catch(err) {
console.log( "** Caught error on errDelay using try{}catch{}:" );
console.log( " Monitor Status is " + errMonitor.status )
};
console.log("errDelay finished:\n", errDelay);
console.log("errMonitor finished:\n", errMonitor);
clearInterval(tickerID);
}
/**
* Creates a new promise with a specific result
* #param {*} tt
* #param {*} exitType ("resolve", "reject" or "error")
*/
function prmDelay (tt, exitType) {
return new Promise(function(resolve, reject) {
if( exitType == 'reject' ){
setTimeout(()=>{ reject("REJECTED")}, tt);
} else if( exitType== 'error'){
setTimeout(()=>{ reject(new Error( "ERROR Rejection") ); }, tt);
} else {
setTimeout(()=>{ resolve("RESOLVED") }, tt);
} ;
});
};
run();
You can add a method to Promise.prototype. It looks like this:
Edited: The first solution is not working properly, like most of the answers here. It returns "pending" until the asynchronous function ".then" is invoked, which is not happen immediately. (The same is about solutions using Promise.race). My second solution solves this problem.
if (window.Promise) {
Promise.prototype.getState = function () {
if (!this.state) {
this.state = "pending";
var that = this;
this.then(
function (v) {
that.state = "resolved";
return v;
},
function (e) {
that.state = "rejected";
return e;
});
}
return this.state;
};
}
You can use it on any Promise. For exemple:
myPromise = new Promise(myFunction);
console.log(myPromise.getState()); // pending|resolved|rejected
Second (and correct) solution:
if (window.Promise) {
Promise.stateable = function (func) {
var state = "pending";
var pending = true;
var newPromise = new Promise(wrapper);
newPromise.state = state;
return newPromise;
function wrapper(resolve, reject) {
func(res, rej);
function res(e) {
resolve(e);
if (pending) {
if (newPromise)
newPromise.state = "resolved";
else
state = "resolved";
pending = false;
}
}
function rej(e) {
reject(e);
if (pending) {
if (newPromise)
newPromise.state = "rejected";
else
state = "rejected";
pending = false;
}
}
}
};
}
And use it:
Notice: In this solution you doesn't have to use the "new" operator.
myPromise = Promise.stateable(myFunction);
console.log(myPromise.state); // pending|resolved|rejected
Caveat: This method uses undocumented Node.js internals and could be changed without warning.
In Node you can synchronously determine a promise's state using process.binding('util').getPromiseDetails(/* promise */);.
This will return:
[0, ] for pending,
[1, /* value */] for fulfilled, or
[2, /* value */] for rejected.
const pending = new Promise(resolve => setTimeout(() => resolve('yakko')));;
const fulfilled = Promise.resolve('wakko');
const rejected = Promise.reject('dot');
[pending, fulfilled, rejected].forEach(promise => {
console.log(process.binding('util').getPromiseDetails(promise));
});
// pending: [0, ]
// fulfilled: [1, 'wakko']
// rejected: [2, 'dot']
Wrapping this into a helper function:
const getStatus = promise => ['pending', 'fulfilled', 'rejected'][
process.binding('util').getPromiseDetails(promise)[0]
];
getStatus(pending); // pending
getStatus(fulfilled); // fulfilled
getStatus(rejected); // rejected
There's another elegant & hacky way of checking if a promise is still pending just by converting the whole object to string and check it with the help of inspect like this: util.inspect(myPromise).includes("pending").
Tested on Node.js 8,9,10,11,12,13
Here's a full example
const util = require("util")
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
(async ()=>{
let letmesleep = sleep(3000)
setInterval(()=>{
console.log(util.inspect(letmesleep).includes("pending"))
},1000)
})()
Result:
true
true
false
false
false
what you can do, is to use a variable to store the state, manually set the state to that variable, and check that variable.
var state = 'pending';
new Promise(function(ff, rjc) {
//do something async
if () {//if success
state = 'resolved';
ff();//
} else {
state = 'rejected';
rjc();
}
});
console.log(state);//check the state somewhere else in the code
of course, this means you must have access to the original code of the promise. If you don't, then you can do:
var state = 'pending';
//you can't access somePromise's code
somePromise.then(function(){
state = 'resolved';
}, function() {
state = 'rejected';
})
console.log(state);//check the promise's state somewhere else in the code
My solution is more coding, but I think you probably wouldn't have to do this for every promise you use.
As of Node.js version 8, you can now use the wise-inspection package to synchronously inspect native promises (without any dangerous hacks).
I made a package for this. Unlike most of the other answers here, it doesn't swallow unhandled rejections.
npm install p-state
import timers from 'timers/promises';
import {promiseStateSync} from 'p-state';
const timeoutPromise = timers.setTimeout(100);
console.log(promiseStateSync(timeoutPromise));
//=> 'pending'
await timeoutPromise;
console.log(promiseStateSync(timeoutPromise));
//=> 'fulfilled'
It looks like somehow nobody came up with one of the simplest solution that doesn't require any hacks:
define a variable to indicate that the promise is running
Add a .finally clause to the promise that sets the variable to false (you can do it at any time after the promise is created)
After that in your code just check if the above variable is true or false, to see whether the Promise is still running.
If you want to know not just whether it's finished or not then instead of .finally add a .then and a .catch clauses that set the variable to "resolved" or "rejected".
The only drawback is that the state variable doesn't get set right away (synchronously) when you add the clauses, in case the promise has already finished. Because of this, it's best to add this to the earliest possible place after the creation of the promise.
Example:
async function worker(){
// wait a very short period of time
await (new Promise(resolve => setTimeout(resolve, 100)))
//...
}
const w1=worker()
let w1_running=true
w1.finally( ()=> {w1_running=false});
//...
//Then check if it's running
(async ()=>{
while(true){
if (w1_running) {
console.log("Still Busy :(")
} else {
console.log("All done :)")
break
}
await (new Promise(resolve => setTimeout(resolve, 10)))
}
})()
// Note we need some async action started otherwise the event loop would never reach the code in the function `worker` or in the `.finally` clause
Here is a more fleshed out es6 version of the QueryablePromise, allowing the ability to chain then and catch after the first resolve and to immediately resolve or reject to keep the api consistent with the native Promise.
const PROMISE = Symbol('PROMISE')
const tap = fn => x => (fn(x), x)
const trace = label => tap(x => console.log(label, x))
class QueryablePromise {
resolved = false
rejected = false
fulfilled = false
catchFns = []
constructor(fn) {
this[PROMISE] = new Promise(fn)
.then(tap(() => {
this.fulfilled = true
this.resolved = true
}))
.catch(x => {
this.fulfilled = true
this.rejected = true
return Promise.reject(x)
})
}
then(fn) {
this[PROMISE].then(fn)
return this
}
catch(fn) {
this[PROMISE].catch(fn)
return this
}
static resolve(x) {
return new QueryablePromise((res) => res(x))
}
static reject(x) {
return new QueryablePromise((_, rej) => rej(x))
}
}
const resolvedPromise = new QueryablePromise((res) => {
setTimeout(res, 200, 'resolvedPromise')
})
const rejectedPromise = new QueryablePromise((_, rej) => {
setTimeout(rej, 200, 'rejectedPromise')
})
// ensure our promises have not been fulfilled
console.log('test 1 before: is resolved', resolvedPromise.resolved)
console.log('test 2 before: is rejected', rejectedPromise.rejected)
setTimeout(() => {
// check to see the resolved status of our promise
console.log('test 1 after: is resolved', resolvedPromise.resolved)
console.log('test 2 after: is rejected', rejectedPromise.rejected)
}, 300)
// make sure we can immediately resolve a QueryablePromise
const immediatelyResolvedPromise = QueryablePromise.resolve('immediatelyResolvedPromise')
// ensure we can chain then
.then(trace('test 3 resolved'))
.then(trace('test 3 resolved 2'))
.catch(trace('test 3 rejected'))
// make sure we can immediately reject a QueryablePromise
const immediatelyRejectedPromise = QueryablePromise.reject('immediatelyRejectedPromise')
.then(trace('test 4 resolved'))
.catch(trace('test 4 rejected'))
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>
2019:
The simple way to do that as I know is thenable , super thin wrapper around promise or any async job.
const sleep = (t) => new Promise(res => setTimeout(res,t));
const sleeping = sleep(30);
function track(promise){
let state = 'pending';
promise = promise.finally( _=> state ='fulfilled');
return {
get state(){return state},
then: promise.then.bind(promise), /*thentable*/
finally:promise.finally.bind(promise),
catch:promise.catch.bind(promise),
}
}
promise = track(sleeping);
console.log(promise.state) // pending
promise.then(function(){
console.log(promise.state); // fulfilled
})
You can extend the Promise class to create a new queryable Promise
class.
You can create your own subclass, say QueryablePromise, by inheriting from the natively available Promise class, the instances of which would have a status property available on it that you can use to query the status of the promise objects synchronously. An implementation of it can be seen below or refer this for a better explanation.
class QueryablePromise extends Promise {
constructor (executor) {
super((resolve, reject) => executor(
data => {
resolve(data)
this._status = 'Resolved'
},
err => {
reject(err)
this._status = 'Rejected'
},
))
this._status = 'Pending'
}
get status () {
return this._status
}
}
// Create a promise that resolves after 5 sec
var myQueryablePromise = new QueryablePromise((resolve, reject) => {
setTimeout(() => resolve(), 5000)
})
// Log the status of the above promise every 500ms
setInterval(() => {
console.log(myQueryablePromise.status)
}, 500)
CAVEAT: process.binding('util').getPromiseDetails is undefined on node 16!
Benchmark:
Candidates:
/**
* https://stackoverflow.com/a/47009572/5318303
*/
const isPromisePending1 = (() => { // noinspection JSUnresolvedFunction
const util = process.binding('util') // noinspection JSUnresolvedFunction
return promise => !util.getPromiseDetails(promise)[0]
})()
/**
* https://stackoverflow.com/a/35852666/5318303
*/
const isPromisePending2 = (promise) => util.inspect(promise) === 'Promise { <pending> }'
/**
* https://stackoverflow.com/a/35820220/5318303
*/
const isPromisePending3 = (promise) => {
const t = {}
return Promise.race([promise, t])
.then(v => v === t, () => false)
}
Test promises:
const a = Promise.resolve()
const b = Promise.reject()
const c = new Promise(() => {})
const x = (async () => 1)()
Run benchmark:
const n = 1000000
console.time('isPromisePending1')
for (let i = 0; i < n; i++) {
isPromisePending1(a)
isPromisePending1(b)
isPromisePending1(c)
isPromisePending1(x)
}
console.timeEnd('isPromisePending1')
console.time('isPromisePending2')
for (let i = 0; i < n; i++) {
isPromisePending2(a)
isPromisePending2(b)
isPromisePending2(c)
isPromisePending2(x)
}
console.timeEnd('isPromisePending2')
console.time('isPromisePending3')
for (let i = 0; i < n; i++) {
await isPromisePending3(a)
await isPromisePending3(b)
await isPromisePending3(c)
await isPromisePending3(x)
}
console.timeEnd('isPromisePending3')
Result:
isPromisePending1: 440.694ms
isPromisePending2: 3.354s
isPromisePending3: 4.761s
Obviously isPromisePending1() is too faster (8~10 times)! But it's not usable on node 16! (see above caveat).
If you're using ES7 experimental you can use async to easily wrap the promise you want to listen.
async function getClient() {
let client, resolved = false;
try {
client = await new Promise((resolve, reject) => {
let client = new Client();
let timer = setTimeout(() => {
reject(new Error(`timeout`, 1000));
client.close();
});
client.on('ready', () => {
if(!resolved) {
clearTimeout(timer);
resolve(client);
}
});
client.on('error', (error) => {
if(!resolved) {
clearTimeout(timer);
reject(error);
}
});
client.on('close', (hadError) => {
if(!resolved && !hadError) {
clearTimeout(timer);
reject(new Error("close"));
}
});
});
resolved = true;
} catch(error) {
resolved = true;
throw error;
}
return client;
}
I've written a little npm package, promise-value, which provides a promise wrapper with a resolved flag:
https://www.npmjs.com/package/promise-value
It also gives synchronous access to the promise value (or error). This doesn't alter the Promise object itself, following the wrap rather than extend pattern.
This is older question but I was trying to do something similar. I need to keep n workers going. They are structured in a promise. I need to scan and see if they are resolved, rejected or still pending. If resolved, I need the value, if rejected do something to correct the issue or pending. If resolved or rejected I need to start another task to keep n going. I can't figure a way to do it with Promise.all or Promise.race as I keep working promises in an array and can find no way to delete them. So I create a worker that does the trick
I need a promise generator function that returns a promise which resolves or rejects as necessary. It is called by a function that sets up the framework to know what the promise is doing.
In the code below the generator simply returns a promise based on setTimeout.
Here it is
//argObj should be of form
// {succeed: <true or false, nTimer: <desired time out>}
function promiseGenerator(argsObj) {
let succeed = argsObj.succeed;
let nTimer = argsObj.nTimer;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (succeed) {
resolve('ok');
}
else {
reject(`fail`);
}
}, nTimer);
})
}
function doWork(generatorargs) {
let sp = { state: `pending`, value: ``, promise: "" };
let p1 = promiseGenerator(generatorargs)
.then((value) => {
sp.state = "resolved";
sp.value = value;
})
.catch((err) => {
sp.state = "rejected";
sp.value = err;
})
sp.promise = p1;
return sp;
}
doWork returns an object containing the promise and the its state and returned value.
The following code runs a loop that tests the state and creates new workers to keep it at 3 running workers.
let promiseArray = [];
promiseArray.push(doWork({ succeed: true, nTimer: 1000 }));
promiseArray.push(doWork({ succeed: true, nTimer: 500 }));
promiseArray.push(doWork({ succeed: false, nTimer: 3000 }));
function loopTimerPromise(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, delay)
})
}
async function looper() {
let nPromises = 3; //just for breaking loop
let nloop = 0; //just for breaking loop
let i;
//let continueLoop = true;
while (true) {
await loopTimerPromise(900); //execute loop every 900ms
nloop++;
//console.log(`promiseArray.length = ${promiseArray.length}`);
for (i = promiseArray.length; i--; i > -1) {
console.log(`index ${i} state: ${promiseArray[i].state}`);
switch (promiseArray[i].state) {
case "pending":
break;
case "resolved":
nPromises++;
promiseArray.splice(i, 1);
promiseArray.push(doWork({ succeed: true, nTimer: 1000 }));
break;
case "rejected":
//take recovery action
nPromises++;
promiseArray.splice(i, 1);
promiseArray.push(doWork({ succeed: false, nTimer: 500 }));
break;
default:
console.log(`error bad state in i=${i} state:${promiseArray[i].state} `)
break;
}
}
console.log(``);
if (nloop > 10 || nPromises > 10) {
//should do a Promise.all on remaining promises to clean them up but not for test
break;
}
}
}
looper();
Tested in node.js
BTW Not in this answer so much but in others on similar topics, I HATE it when someone says "you don't understand" or "that's not how it works" I generally assume the questioner knows what they want. Suggesting a better way is great. A patient explanation of how promises work would also be good.
Old question with many answers but none seem to suggest what I think is the simplest solution: set a bool indicator on promise resolution/rejection.
class Promise2 {
constructor(...args) {
let promise = new Promise(...args);
promise.then(() => promise._resolved_ = true);
promise.catch(() => promise._rejected_ = true);
return promise;
}
}
let p = new Promise2(r => setTimeout(r, 3000));
setInterval(() => {
console.log('checking synchronously if p is resolved yet?', p._resolved_);
}, 1000);
This is the Future pattern I use: (https://github.com/Smallscript-Corp)
enables sync and async fn usage
enables event patterns to be unified with async behavior
class XPromise extends Promise {
state = 'pending'
get settled() {return(this.state !== 'pending')}
resolve(v,...a) {
this.state = 'resolved'
return(this.resolve_(this.value = v,...a))
}
reject(e,...a) {
this.state = 'rejected'
return(this.reject_(this.value = (e instanceof Error) ? e : XPromise.Error(e),...a))
}
static Error(e) {const v = Error('value-rejected'); v.value = e; return(v)}
static Future(fn,...args) { // FactoryFn
let r,t,fv = new XPromise((r_,t_) => {r=r_;t=t_})
fv.resolve_ = r; fv.reject_ = t;
switch(typeof fn) {
case 'undefined': break; case 'function': fn(fv,...args); break;
default: fv.resolve(fn)
}
return(fv)
}
}
global.Future = XPromise.Future
Then you can create future-value instances that can be resolved using sync and async functions; enables handling events uniformly.
You can use it to write a pattern like:
async doSomething() {
// Start both - logically async-parallel
const fvIsNetworkOnLine = this.fvIsNetworkOnline
const fvAuthToken = this.fvAuthToken
// await both (order not critical since both started/queued above)
await fvAuthToken
await fvIsNetworkOnLine
// ... we can check the future values here if needed `fv.resolved`, `fv.state` etc
// ... do dependent workflow here ...
}
onNetworkOnLine(fIsOnline) {
// We utilize the `fv.settled` below, and use the event to `settle` it etc
if(fIsOnline) {
if(this.fvNetworkAvailable_)
this.fvNetworkAvailable_.resolve(true)
this.fvNetworkAvailable_ = undefined
}
else if(this.fvNetworkAvailable_.settled) {
this.fvNetworkAvailable_ = undefined
}
}
get fvNetworkAvailable() {
if(navigator.onLine)
return true
else if(this.fvNetworkAvailable_)
return this.fvNetworkAvailable_
return (this.fvNetworkAvailable_ = Future())
}
get fvAuthToken() {
if(this.fvAuthToken_)
return this.fvAuthToken_
const authTokenFv = async fv => {
// ... handle retry logic etc here ...
}
return(this.fvAuthToken_ = Future(authTokenFv))
}
I found this solution to be simple and allow me to continue using native promises but add useful synchronous checks. I also didn't have to pull in an entire promise library.
CAVEAT: This only works if there is some sort of break in the current execution thread to allow the promises to execute BEFORE checking the synchronous constructs. That makes this of more limited usefulness than I'd initially thought -- still useful for my use case though (Thanks Benjamin Gruenbaum for pointing this out)
/**
* This function allow you to modify a JS Promise by adding some status properties.
* Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved
* But modified according to the specs of promises : https://promisesaplus.com/
*/
function MakeQuerablePromise(promise) {
// Don't modify any promise that has been already modified.
if (promise.isFulfilled) return promise;
// Set initial state
var isPending = true;
var isRejected = false;
var isFulfilled = false;
// Observe the promise, saving the fulfillment in a closure scope.
var result = promise.then(
function(v) {
isFulfilled = true;
isPending = false;
return v;
},
function(e) {
isRejected = true;
isPending = false;
throw e;
}
);
result.isFulfilled = function() { return isFulfilled; };
result.isPending = function() { return isPending; };
result.isRejected = function() { return isRejected; };
return result;
}
wrappedPromise = MakeQueryablePromise(Promise.resolve(3));
setTimeout(function() {console.log(wrappedPromise.isFulfilled())}, 1);
From https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved which based their answer on Is there a way to tell if an ES6 promise is fulfilled/rejected/resolved?

Categories

Resources