简体   繁体   English

使用带有promise的forEach(),而访问前一个promise会导致.then()链?

[英]use forEach() with promises while access previous promise results in a .then() chain?

I have the following functions with promises: 我有承诺的以下功能:

const ajaxRequest = (url) => {
  return new Promise(function(resolve, reject) {
    axios.get(url)
      .then((response) => {
        //console.log(response);
        resolve(response);
      })
      .catch((error) => {
        //console.log(error);
        reject();
      });
  });
}


const xmlParser = (xml) => {
  let { data } = xml;
  return new Promise(function(resolve, reject) {
    let parser = new DOMParser();
    let xmlDoc = parser.parseFromString(data,"text/xml");

    if (xmlDoc.getElementsByTagName("AdTitle").length > 0) {
      let string = xmlDoc.getElementsByTagName("AdTitle")[0].childNodes[0].nodeValue;
      resolve(string);
    } else {
      reject();
    }
  });
}

I'm trying to apply those functions for each object in array of JSON: 我正在尝试为JSON数组中的每个对象应用这些函数:

const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]

I came up with the following solution: 我提出了以下解决方案:

function example() {
    _.forEach(array, function(value) {
        ajaxRequest(value.url)
            .then(response => {
                xmlParser(response)
            .catch(err => {
                console.log(err);
            });
        });
    }
 }

I was wondering if this solution is acceptable regarding 2 things: 我想知道这个解决方案是否可以接受两件事:

  1. Is it a good practice to apply forEach() on promises in the following matter. 在以下事项中对承诺申请Each()是一种好的做法。

  2. Are there any better ways to pass previous promise results as parameter in then() chain? 有没有更好的方法将先前的promise结果作为then()链中的参数传递? (I'm passing response param). (我正在通过response )。

You can use .reduce() to access previous Promise . 您可以使用.reduce()来访问以前的Promise

function example() {
    return array.reduce((promise, value) =>
       // `prev` is either initial `Promise` value or previous `Promise` value
       promise.then(prev => 
         ajaxRequest(value.url).then(response => xmlParser(response))
       )
    , Promise.resolve())
 }
 // though note, no value is passed to `reject()` at `Promise` constructor calls
 example().catch(err => console.log(err)); 

Note, Promise constructor is not necessary at ajaxRequest function. 注意,在ajaxRequest函数中不需要Promise构造函数。

const ajaxRequest = (url) => 
    axios.get(url)
      .then((response) => {
        //console.log(response);
        return response;
      })
      .catch((error) => {
        //console.log(error);
      });

The only issue with the code you provided is that result from xmlParser is lost, forEach loop just iterates but does not store results. 您提供的代码的唯一问题是xmlParser的结果丢失,forEach循环只是迭代但不存储结果。 To keep results you will need to use Array.map which will get Promise as a result, and then Promise.all to wait and get all results into array. 为了保持结果,你需要使用Array.map作为结果得到Promise,然后Promise.all等待并将所有结果都放到数组中。

I suggest to use async/await from ES2017 which simplifies dealing with promises. 我建议使用ES2017的async / await来简化处理promises。 Since provided code already using arrow functions, which would require transpiling for older browsers compatibility, you can add transpiling plugin to support ES2017. 由于提供的代码已经使用了箭头函数,这需要转换为较旧的浏览器兼容性,因此您可以添加转换插件以支持ES2017。

In this case your code would be like: 在这种情况下,您的代码将是:

function example() {
  return Promise.all([
    array.map(async (value) => {
      try {
        const response = await ajaxRequest(value.url);
        return xmlParser(response);
      } catch(err) {
        console.error(err);
      }
    })
  ])
}

Above code will run all requests in parallel and return results when all requests finish. 上面的代码将并行运行所有请求,并在所有请求完成后返回结果。 You may also want to fire and process requests one by one, this will also provide access to previous promise result if that was your question: 您可能还希望逐个触发和处理请求,如果这是您的问题,这也将提供对先前承诺结果的访问:

async function example(processResult) {
  for(value of array) {
    let result;
    try {
      // here result has value from previous parsed ajaxRequest.
      const response = await ajaxRequest(value.url);
      result = await xmlParser(response);
      await processResult(result);
    } catch(err) {
      console.error(err);
    }
  }
}

Another solution is using Promise.all for doing this, i think is a better solution than looping arround the ajax requests. 另一个解决方案是使用Promise.all来做这件事,我认为这是一个比循环ajax请求更好的解决方案。

const array = [{"id": 1, "url": "www.link1.com"}, {"id": 1, "url": "www.link2.com"}]

function example() {
    return Promise.all(array.map(x => ajaxRequest(x.url)))
        .then(results => {
            return Promise.all(results.map(data => xmlParser(data)));
        });
}

example().then(parsed => {
    console.log(parsed); // will be an array of xmlParsed elements
});

Are there any better ways to pass previous promise results as parameter in then() chain? 有没有更好的方法将先前的promise结果作为then()链中的参数传递?

In fact, you can chain and resolve promises in any order and any place of code. 事实上,您可以按任何顺序和代码的任何位置链接和解决承诺。 One general rule - any chained promise with then or catch branch is just new promise, which should be chained later. 一个通用规则 - 使用thencatch分支的任何链式承诺只是新的承诺,应该在以后链接。

But there are no limitations. 但没有限制。 With using loops, most common solution is reduce left-side foldl, but you also can use simple let -variable with reassign with new promise. 使用循环,最常见的解决方案是reduce左侧foldl,但您也可以使用简单的let -variable并重新分配新的承诺。

For example, you can even design delayed promises chain: 例如,您甚至可以设计延迟的承诺链:

function delayedChain() {
  let resolver = null
  let flow = new Promise(resolve => (resolver = resolve));
  for(let i=0; i<100500; i++) {
    flow = flow.then(() => {
      // some loop action
    })
  }

  return () => {
    resolver();
    return flow;
  }
}

(delayedChain())().then((result) => {
  console.log(result)
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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