1

I have a function (that contains promises internally so it itself runs synchronously) that seems to be running asynchronously within my main code. No matter how I format my promise it seems like the resolve gets sent before the functions ends execution:

This problem is also logically recursive, in that if I try adding another promise around the nameExists function (within this very promise) and then putting the resolve in a 'then', i just run into the same issue with the nested resolve...

    document.getElementById("config-select").addEventListener("input", function(){
      //check if the doc name exists: returns doc id
      //promise that doc_obj is created before moving on
      let doc_obj = {};
      let promise = new Promise(function (resolve, reject) {
        let doc_name = document.getElementById("config-select").value;
        doc_obj = nameExists(doc_name);
        resolve('done'); //this executes BEFORE nameExists is done processing...bringing back the original asynch issue i was trying to fix in the first place...
      });
      promise.then(function (result) {
          alert("then: "+doc_obj);
          if(doc_obj.bool === true){//it does exist:
            alert("replacing id");
            document.getElementById("config-select").setAttribute("doc-id", doc_obj.id);
          }
          else{//it doesn't:
            alert("resetting id");
            document.getElementById("config-select").setAttribute("doc-id", "");
          }
        }
      );

    });

The nameExists function:

//check if the name in config-select is an existing doc (assumes name is a unique document field)
const nameExists = function(name){
  //get all docs
  localDB.allDocs({include_docs: true}).then(function (result) {
    //return object set to default state if no match is found
    let doc_obj = {bool: false, id: ""};
    alert("Entering the match checker...");

    for(let i =0; i<result.total_rows; i++) {
      if(result.rows[i].doc.name == name){
        alert(result.rows[i].doc.name);
        alert(name);
        doc_obj.bool = true;
        doc_obj.id = result.rows[i].doc._id;
        //found a match
        break;
      }
    }
    //return the result
    alert("returned obj.id: "+doc_obj.bool);
    return doc_obj;

  }).catch(function (err) {console.log(err);});
};

Ideally, I would like the doc_obj or some return value object to be populated with data from the nameExists function, before evaluating my 'if statements'. How can I format my promise/resolve statement to achieve this?

Aalok Borkar
  • 95
  • 1
  • 7
  • The promise in your question doesn't serve any purpose I can discern. Why wrap those few lines in a promise? –  Jul 18 '19 at 19:00
  • @Amy - possibly because coming up with a useful yet non-trivial example is really, really hard. – JDB Jul 18 '19 at 19:05
  • @JDB I think its a question only the OP can answer, then. –  Jul 18 '19 at 19:07
  • 1
    @Aalok - Can you clarify what you expected the result to be? From reading your code, it looks like you are creating a promise and then immediately resolving it with the return value of `nameExists` (which you haven't included in your question). But maybe you were expecting a different outcome? – JDB Jul 18 '19 at 19:08
  • There is no point in use `new Promise` here, since you are immediately resolving it anyway. If something is not working, it must be the fault of that `nameExists` function (which should return a promise for its asynchronous result). Please post its code. – Bergi Jul 18 '19 at 19:57
  • The issue here is that nameExists() contains an asynch database call, so when I call it in my main code, the next line of code is being run while nextExists() processes. This is an issue in my original code since im assigning an object as the result of nameExist and trying to evaluate it through if statement immediately after (where its read as undefined since nameExists hasnt finished processing). Hopefully you can see why I thought to use a promise: I wanted to have the object variable, doc_obj, be fully populated with nameExists's result before evaluating it through if statements. – Aalok Borkar Jul 18 '19 at 20:18
  • @Amy I edited my code to show my original thought process, I want to be able to gaurentee that doc_obj is fully populated before evaluation (if statements), however i dont know where to place the promise's resolve() so that it resolves only AFTER nameExist() is done processing. In what I originally posted (pre-edit): I tried putting the nameExists output (object) within the resolve as to guarantee that it would 'resolve' only after nameExists was done processing, however, that didnt seem to be the case and it was still finishing the promise with an undefined value... – Aalok Borkar Jul 18 '19 at 20:27
  • @AalokBorkar If `nameExists` returns a `promise`, then why not `nameExists(....).then(...)`? –  Jul 18 '19 at 20:28
  • It does not return a promise, it returns an object: let doc_obj = {bool: false, id: ""}; Should I make it return a promise? will that enable me to use .then() ? – Aalok Borkar Jul 18 '19 at 20:31
  • 1
    @AalokBorkar Yes, thinking of promises was right, but you need to use the promise *inside* `nameExists` so that it can return a promise for its asynchronous result. If you try to synchronously return values from `nameExists`, you will always get `undefined`. Again, please post the code of your `nameExists` function or we cannot help you with your issue. Also make sure to read [our canonical](https://stackoverflow.com/q/23667086/1048572) [questions](https://stackoverflow.com/q/14220321/1048572) on the topic. – Bergi Jul 18 '19 at 20:35
  • 1
    You first sentence doesn't make a lot of sense: "I have a function (that contains promises internally so it itself runs synchronously)". Containing promises internally indicates it is in fact asynchronous in nature, not that it is synchronous. – Heretic Monkey Jul 18 '19 at 20:35
  • @Bergi I have pasted the nameExist function – Aalok Borkar Jul 18 '19 at 20:41
  • 1
    `nameExists` needs to return its promise. `return localDB.allDocs(...` Then you can `nameExists(...).then(...)` –  Jul 18 '19 at 20:43

2 Answers2

1

You should drop that new Promise - it doesn't change anything about whether you will be able to wait for nameExists' result or not. You will need to return the promise that then() creates inside the nameExists function:

function nameExists(name) {
  return localDB.allDocs({include_docs: true}).then(function (result) {
//^^^^^^
    for (let i =0; i<result.total_rows; i++) {
      if (result.rows[i].doc.name == name){
        return {bool: true, id: result.rows[i].doc._id};
      }
    }
    return {bool: false, id: ""};
  });
//  ^ don't catch errors here if you cannot handle them and provide a fallback result
}

Then you can just wait for it in your event listener:

document.getElementById("config-select").addEventListener("input", function() {
  const doc_select = document.getElementById("config-select");
  const doc_name = doc_select.value;
  // check if the doc name exists: returns doc id
  nameExists(doc_name).then(function(doc_obj) {
//^^^^^^^^^^^^^^^^^^^^^^^^^^          ^^^^^^^
    console.log("then", doc_obj);
    if (doc_obj.bool) { // it does exist:
      alert("replacing id");
    } else { // it doesn't:
      alert("resetting id");
    }
    doc_select.setAttribute("doc-id", doc_obj.id); // id is "" when it doesn't exist
  }).catch(function (err) {
    console.log(err);
  })
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Awesome, you are truly a Javascript wizard. This implementation works perfectly, and my app is behaving exactly as intended. The refactoring was also a nice touch, thanks ;) – Aalok Borkar Jul 18 '19 at 21:03
0

the only async call you have is inside the nameExists function which is the database call so there is no need to write two promises, only one is enough to solve your issue.

the first event should be like that:

document.getElementById("config-select").addEventListener("input", function(){
   nameExists(doc_name).then(function(doc_obj) {
       alert("then: "+doc_obj);
       if(doc_obj.bool === true){//it does exist:
          alert("replacing id");
          document.getElementById("config-select").setAttribute("doc-id",  doc_obj.id);
       }
       else{//it doesn't:
          alert("resetting id");
          document.getElementById("config-select").setAttribute("doc-id", "");
       }
   }).catch(function (err) { console.log(err) });
});

and the nameExists function should look like that:

//check if the name in config-select is an existing doc (assumes name is a unique document field)
const nameExists = function(name){
  //get all docs
  return localDB.allDocs({include_docs: true}).then(function (result) {
    //return object set to default state if no match is found
    let doc_obj = {bool: false, id: ""};
    alert("Entering the match checker...");

    for(let i =0; i<result.total_rows; i++) {
      if(result.rows[i].doc.name == name){
        alert(result.rows[i].doc.name);
        alert(name);
        doc_obj.bool = true;
        doc_obj.id = result.rows[i].doc._id;
        //found a match
        break;
      }
    }
    //return the result
    alert("returned obj.id: "+doc_obj.bool);
    return(doc_obj); // here is where the code runs then statement inside the event

 });      
};
Muho
  • 3,188
  • 23
  • 34
  • 1
    Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it) in `nameExists`! – Bergi Jul 18 '19 at 21:07