简体   繁体   中英

How do I make a nested loop continue only after a asynchronous function has been resolved or how do I extend “.then” beyond the scope

I tried to prevent async problems with promises in the following code. By using a .then function everything within that function gets called after the function has been resolved. But now I have the problem that neither can I extend the scope of the ".then function" enough to include the bits after the second loop nor can I to my knowledge easily pause the code until the function has been properly resolved and THEN continue with the loop iteration.

Here's my main code(simplified):

let total = []
$.each(element, function(data) {
  //Some other code
  let out;
  $.each(element2, function(data2) {
    getZip(data2).then(function(txt){ //after everything has finished this get's called
      out = someFunction(txt,data2);
      total.push(out);
    });

  )};
   console.log(total)//this gets called first 
  //some other code that does some stuff with total
)};

Here's the getZip code which is asynchronous:

        function getZip(zipFile) {
            return new Promise(function (resolve, reject){
                zip = new JSZip()
                JSZipUtils.getBinaryContent("someURL/" + zipFile, function (err, data) {
                    if (err) {
                        reject(err)
                    }
                    JSZip.loadAsync(data).then(function (zip) {
                        return zip.file(zipFile.replace(".zip", "")).async("text"); //gets the file within the zip andoutputs as text
                    }).then(function (txt) {
                        resolve(txt)
                    });

                });
            });
        }

I'd be happy if either the getZip code could be made synchronous or if the before mentioned could be done.

I do not think I fully understand the code you have written. However, I recommend you use Promise.all . Here is an example I have written that I hope helps guide you:

 let total = []; $.each([1,2,3,4], function (data) { // Some other code. let out; // Create a new promise so that we can wait on the getZip method. new Promise(function (resolve, reject) { // Create a holder variable. This variable with hold all the promises that are output from the getZip method you have. let gZipPromises = []; $.each([5,6,7,8], function (data2) { // Your getZip method would go here. wrap the call to getZip in gZipPromises.push to push all the returned promises onto the holding variable. gZipPromises.push(new Promise(function (resolve2, reject2) { // Sample Code setTimeout(function () { total.push(data2); resolve2(""); }, 10); // End Sample Code. })); }); // Pass the holding variable to Promise.all so that all promises in the holding variable are executed before resolving. Promise.all(gZipPromises).then(function() { resolve() }); }).then(function () { // This will be called only when all getZip promises are completed in the second loop. console.log(total); }); }); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> 

With that said, I could not test your code. But I think this would work: (Please note that based on the code you provided, the variable total would be logged for each iteration of the top most $.each

 let total = [] $.each(element, function(data) { //Some other code let out; // Define a new promise. new Promise(function (resolve, reject) { let gZipPromises = []; $.each(element2, function(data2) { gZipPromises.push( getZip(data2).then(function(txt){ //after everything has finished this get's called out = someFunction(txt,data2); total.push(out); }); ); )}; Promise.all(gZipPromises).then(function() { resolve() }); }).then(function () { console.log(total) }); )}; 

 const elements = [["foo.zip"],["bar.zip"],["baz.zip"]]; const totalOut = getAllZips(elements) .then(text => console.info(text)) .catch(error => console.error(error)) function someFunction(text, data) { return `${text}\\nLength: ${data.length}`; } async function getAllZips(elements) { let promises = []; for(const element of elements) { for(const data of element) { promises.push(getZip(data).then(text => { return someFunction(text, data); })); } } return Promise.all(promises); } async function getZip(file) { return new Promise((resolve, reject) => { JSZipUtils.getBinaryContent(`someURL/${file}`, async (err, data) => { try { if (err) throw err; const zip = await JSZip.loadAsync(data); const name = file.replace(".zip", ""); resolve(await zip.file(name).async('text')); } catch(error) { reject(error); } }); }); } 
 <script>/*IGNORE*/const JSZipUtils = {getBinaryContent:(p,c)=>errs.gbc?c(new Error('gbc'),null):c(null,{foo:true})};const JSZip = {loadAsync:(d)=>errs.la?Promise.reject(new Error('la')):({file:n=>({async:a=>errs.a?Promise.reject(new Error('a')):Promise.resolve('Hello World')})})};const errs = {gbc:false,la:false,a:false};/*IGNORE*/</script> 

This kind of sounds like a use case for async iterator generators, but maybe I'm just over-engineering. You have a bunch of resources that you want to iterate over and their contents are asynchronous. You want it to "look" synchronous, so you can leverage async/await:

 function getZip(zipFile) { /* * Theres no point in simplifying this function since it looks like * the JSZip API deals with callbacks and not Promises. */ return Promise.resolve(zipFile); } function someFn(a, b) { return `${a}: ${b.length}`; } async function* zipper(elements) { for (const element of elements) { for (const data of element) { const txt = await getZip(data); yield someFn(txt, data); } } } (async() => { const elements = [ ["hello"], ["world"], ["foo"], ["bar"] ]; let total = []; for await (const out of zipper(elements)) { total.push(out); } console.log(total); })(); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM