0

I think I understand promises, but I can't seem to get my head around it correctly. I have an application built in SharePoint 2013 that does a REST call via sprLib to populate an object. The object can be manipulated on the page, either entries added/deleted/updated, basically full CRUD. Then the changes are pushed back to the list. So far, this is all working smoothly. What I am trying to do now is on the update, empty the div tag, then call the initial load function again to populate the div with the new current list. Right now, either I get an error on the promise syntax or race condition where the initial load function runs again before the update finishes, so the populated list is the old list. I created a basic version to simulate the idea, minus the REST updates since I couldn't find a way to replicate that in a sandbox.

https://jsfiddle.net/36muca0g/

Long story short, 3 things should happen: 1) initial load 2) changes made 3) re-load data and empty/append html accordingly.

const fullList = [                             //In real environment, this is populate via REST operation.
    { id: "12", title: "A", code: "110" },
    { id: "23", title: "B", code: "120" },
    { id: "13", title: "C", code: "130" },
    { id: "43", title: "D", code: "140" },
    { id: "52", title: "E", code: "150" },
]

$(document).ready(function () {
    initialLoad(fullList);

    $('#updateList').click(function() {
        let item = $('#txtValue').val();
        addToList(item).then(function(result){
            console.log('add complete');
            initialLoad(fullList);
            console.log('reloaded');
        })
        .catch(function(){
            console.log('fail');
        })
    });
})

function initialLoad(list) {
    //In real environment, this is a getItem operation using sprLib.
    $('#itemList').empty();
    let listHTML = ''
    for (i = 0; i < list.length; i++) {
        listHTML += '<p>'+list[i].title+' ' +list[i].code+'</p>'
    }
    $('#itemList').append(listHTML);
}

function addToList(entry) {
    return new Promise(function(resolve, reject) {
        console.log('I go first');
        fullList.push({ 'id': "", 'title': entry, 'code': ""}); //In real environment, this is an CRUD operation using sprLib.
        setTimeout(() => resolve("done"), 5000);
    });
}

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise</title>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" type="text/javascript"></script>    
    <script src="https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js" type="text/javascript"></script>
    <script src="/scripts/promise.js"></script>
    <link href="https://cdn.datatables.net/1.12.1/css/jquery.dataTables.min.css" rel="stylesheet">
    <link href="https://cdn.datatables.net/select/1.4.0/css/select.dataTables.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">

</head>
<body><br><br>
    <div class="container text-center row">
        
        <div class="card col-2 bg-primary text-light" id="itemList">
        
        </div>
        
        <div class="card col-2 bg-secondary"><input type="text" class="form" id="txtValue">
        <button type="button" class="btn btn-success" id="updateList">Submit</button></div>
    
    </div>
</body>
</html>
Eric Smith
  • 37
  • 5
  • [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) – Andreas Jul 30 '22 at 14:01
  • "*I get an error on the promise syntax*" - which error? "*…or race condition where the initial load function runs again before the update finishes, so the populated list is the old list.*"- I cannot reproduce that with the code you provided – Bergi Jul 30 '22 at 18:57
  • I updated the code based on Pointy's comment. that fixed the promise syntax error. Right now it's working, but the setTimeout function I used to simulate the REST operation isn't equivalent. I'm trying to update the model to be more accurate to the race condition i'm facing. – Eric Smith Jul 30 '22 at 19:15
  • @EricSmith Pleaes show us your real code then. Apparently you're not updating `fullList` before calling `initialLoad` again? (Btw, you really should remove "initial" from that name if you're running it after every update) – Bergi Jul 31 '22 at 10:15
  • @Bergi Unfortunately my real code is on a closed network so I'm trying to get a minimal working version. – Eric Smith Jul 31 '22 at 11:58

1 Answers1

0

The way i see it, you need a proper "trigger" to replace that ugly setTimeout() in a way that you allow to refresh the view only when the new data is accessible.

If you're working your application around a MCV pattern, what you could do is attach the state of your data onto an element inside the DOM, and listen for this element's state changes and use that to trigger the reload your fresh data.

I handled the case recently by building a drag-&-drop functionality where i wanted to display the content of the file only and only when the reader terminated the load task.

controller.js

const loadState = async function (type, input) {
  return new Promise((resolve, reject) => {
    try {
      const reader = new FileReader();
      if (type === "drop") {
        const data = state.fileItem[0];
        for (let i = 0; i < data.length; i++) {
          if (data[i].kind === "file") {
            const file = data[i].getAsFile();
            reader.onload = async function (e) {
              resolve((state.partNumber = createArr(e.target.result)));
            };
            reader.readAsText(file);
          }
        }
      }
    } catch (err) {
      console.log(err);
    }
  });
};

eventHandlers.dropZone.addEventListener("drop", handleDropImport, false);
async function handleDropImport(e) {
  try {
    e.preventDefault();
    if (!e.dataTransfer.items) return;
    state.fileItem.push(e.dataTransfer.items);
    await loadState("drop");
    eventHandlers.queryPreview.classList.add("uploaded");
  } catch (err) {
    console.log(err);
  }
}

As soon as the handleDropImport() is managed and only after the data is loaded inside the state (thanks to async/await), we add this class to the element of your choice...

eventHandlers.queryPreview.classList.add("uploaded");

You can then scan the DOM for that classList.add in your view.js using a mutation observer, and make it reachable using a publisher/subscriber pattern.

view.js

  _addHandlerRender(handler) {
    const options = { attributes: true, childList: true, subtree: true };
    const observer = new MutationObserver((mutations) =>
      mutations.forEach((mut) => {
        if (mut.type === "attributes" && mut.attributeName === "class") {
          handler();
        }
      })
    );
    observer.observe(this._parentElement, options);
  }

model.js

const controlPreview = async function () {
  try {
    previewViews._clearPreview();
    previewViews.render(model.state.partNumber);
  } catch (err) {
    console.log(err);
  }
};

const init = function () {
  previewViews._addHandlerRender(controlPreview);
};

init();
FraMa
  • 35
  • 3