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

时间:2017-07-25 07:19:43

标签: javascript scope ecmascript-6 promise es6-promise

我有承诺的以下功能:

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();
    }
  });
}

我正在尝试为JSON数组中的每个对象应用这些函数:

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

我提出了以下解决方案:

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

我想知道这个解决方案是否可以接受两件事:

  1. 在以下事项中对承诺申请forEach()是一种好习惯。

  2. 有没有更好的方法将先前的promise结果作为then()链中的参数传递? (我正在通过response param)。

4 个答案:

答案 0 :(得分:4)

您可以使用.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)); 

注意,Promise函数不需要ajaxRequest构造函数。

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

答案 1 :(得分:1)

您提供的代码的唯一问题是xmlParser的结果丢失,forEach循环只是迭代但不存储结果。为了保持结果,你需要使用Array.map作为结果得到Promise,然后Promise.all等待并将所有结果都放到数组中。

我建议使用ES2017的async / await来简化处理promises。由于提供的代码已经使用了箭头函数,这需要转换为旧版浏览器兼容性,因此您可以添加转换插件来支持ES2017。

在这种情况下,您的代码就像:

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

以上代码将并行运行所有请求,并在所有请求完成后返回结果。您可能还希望逐个触发和处理请求,如果这是您的问题,还可以访问以前的承诺结果:

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);
    }
  }
}

答案 2 :(得分:1)

另一个解决方案是使用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
});

答案 3 :(得分:0)

  

有没有更好的方法可以将之前的承诺结果传递给   then()链中的参数?

事实上,您可以按任何顺序和代码的任何位置链接和解决承诺。一个通用规则 - thencatch分支的任何链式承诺只是新的承诺,应该在以后链接。

但没有限制。使用循环时,最常见的解决方案是reduce左侧foldl,但您也可以使用简单的let - 变量重新分配新的承诺。

例如,您甚至可以设计延迟承诺链:

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)
})