承诺不返回预期价值

时间:2018-01-15 01:42:27

标签: javascript node.js

我一直在学习承诺,我有一个问题。我有一个名为getNumber的函数,它返回一个数字数组(为了便于理解)。我使用该函数迭代该数组并为每个值发出一个http请求(使用setTimeout以在调用之间产生延迟)

然后我想使用then函数中收集的信息,但它给了我一个'undefined error'。显然这里有些不对劲,但我看不出来。你知道我怎么能解决这个问题以及出了什么问题?

var getNumbers  = () => {
  return new Promise(function(resolve, reject) {

    console.log("In function getNumbers");
    var data = [1,2,3,4,5,6,7,8,9];
    resolve(data);
  });
};


getNumbers()

    .then(numbersArray => {
        //Supposed to return array of posts title
        return numbersArray.map(number => {
          console.log("Reading number" + number);

            setTimeout(() => {
                //make a http request
                return getHtml("https://jsonplaceholder.typicode.com/posts/"+number)
                    .then(function(post) {
                        return post.title;
                    })

            }, 10000);//make a request each ten seconds
        });
    })
    .then(postTitlesArray => {
    //Shows array of undefined
      console.log(postTitlesArray)

    });



function getHtml(webUrl) {
    return fetch(webUrl)
        .then(function(res) {
            return res.json();
        });
}

3 个答案:

答案 0 :(得分:2)

你的方法做你想做的事情有几个概念性的事情。

首先,.map()是同步的。这意味着它将运行完成,并且不会等待任何异步操作完成。

其次,setTimeout()是非阻塞的。它只是在将来的某个时间安排一个计时器,然后你的.map()回调立即返回,什么都不返回。

所以,你的方法根本不起作用。

根据您的评论,您尝试完成的工作似乎是在一个循环中进行一系列网络呼叫,但在它们之间设置延迟,这样您就不会受到速率限制。有很多方法可以做到这一点。

您需要使用两个基本概念:

  1. 使您的异步操作顺序进行,以便下一个操作无法启动,直到完成前一个操作。

  2. 在开始下一个延迟之前设置一个与promises一起使用的延迟。

  3. 我首先使用async/await展示ES7方法,因为它在概念上看起来可能是最简单的。

    使用async/await对异步数组访问进行排序

    function delay(t) {
        return new Promise(resolve => {
            setTimeout(resolve, t);
        });
    }
    
    getNumbers().then(async function(numbersArray) {
        //Supposed to return array of posts title
        let results = [];
        let delayT = 0;    // first delay is zero
        for (let number of numbersArray) {
            console.log("Reading number" + number);
            let r = await delay(delayT).then(() => {
                delayT = 10 * 1000;   // 10 seconds for subsequent delays
                return getHtml("https://jsonplaceholder.typicode.com/posts/"+number).then(function(post) {
                    return post.title;
                });
            });
            results.push(r);
        }
        return results;
    });
    

    使用.reduce()对异步数组访问进行排序

    如果你想在没有async/await的情况下这样做,那么你可以使用.reduce()设计模式对数组的异步迭代进行排序:

    function delay(t) {
        return new Promise(resolve => {
            setTimeout(resolve, t);
        });
    }
    
    getNumbers().then(numbersArray => {
        //Supposed to return array of posts title
        let results = [];
        let delayT = 0;    // first delay is zero
        return numersArray.reduce((p, number) => {
            return p.then(() => {
                return delay(delayT).then(() => {
                    delayT = 10 * 1000;   // 10 seconds for subsequent delays
                    return getHtml("https://jsonplaceholder.typicode.com/posts/"+number).then(function(post) {
                        results.push(post.title);
                    });
                });
            });
        }, Promise.resolve()).then(() => {
            // make array of results be the resolved value of the returned promise
            return results;
        });
    });
    

    请注意,这两种算法都被编码为不会延迟第一次操作,因为您可能不需要这样做,所以它只会在连续操作之间延迟。

    根据编码,这些是Promise.all()之后的模型,如果您的getHtml()次来电被拒,他们会拒绝。如果你想要返回所有结果,即使有些拒绝,那么你可以改变:

    return getHtml(...).then(...)
    

    return getHtml(...).then(...).catch(err => null);
    

    null放入返回的数组中以查找失败的结果,或者如果要记录错误,可以使用:

    return getHtml(...).then(...).catch(err => {
        console.log(err);
        return null;
    });
    

    通用助手功能

    而且,由于这是一个通用的问题,这里是一个通用的辅助函数,它允许你迭代一个数组,调用数组中每个项目的异步操作,并将所有结果累积到一个数组中:

    // Iterate through an array in sequence with optional delay between each async operation
    // Returns a promise, resolved value is array of results
    async iterateArrayAsync(array, fn, opts = {}) {
        const options = Object.assign({
            continueOnError: true, 
            delayBetweenAsyncOperations: 0,
            errPlaceHolder: null
        }, opts);
        const results = [];
        let delayT = 0;      // no delay on first iteration
        for (let item of array) {
            results.push(await delay(delayT).then(() => {
                return fn(item);
            }).catch(err => {
                console.log(err);
                if (options.continueOnError) {
                    // keep going on errors, let options.errPlaceHolder be result for an error
                    return options.errPlaceHolder;
                } else {
                    // abort processing on first error, will reject the promise
                    throw err;
                }
            }));
            delayT = options.delayBetweenAsyncOperations;   // set delay between requests
        }
        return results;
    }
    

    这接受允许continueOnError的选项,允许您设置每个异步操作之间的延迟,并允许您控制任何失败操作的结果数组中的占位符(仅在设置continueOnError时使用)。所有选项都是可选的。

答案 1 :(得分:1)

我假设您要做的是:1)使用getNumbers获取数字列表。 2)迭代第一步中的每个数字并形成一个URL,每隔十秒就会发出一个http请求。 3)如果请求成功发送,请等待其响应。 4)从响应中获取post.title。 5)等到步骤2中的迭代结束,并返回从每次调用收到的所有post.titles的数组。

考虑到上述假设,我稍微编辑了您的代码,以下解决方案将起作用。见jsfiddle

我认为代码的主要问题是map方法不会返回任何内容。

const getNumbers  = () => {
  return new Promise(function(resolve, reject) {

    console.log("In function getNumbers");
    var data = [1,2,3,4,5,6,7,8,9];
    resolve(data);
  });
};

const delay = (number, t) => {
    return new Promise((resolve) => {
      setTimeout(() => { 
        //make a http request
        resolve(
          getHtml("https://jsonplaceholder.typicode.com/posts/"+number)
          .then(function(post) {
            console.log('title', post.title)
            return post.title;
          })
        )
      }, t)
    })

}

const getHtml = (webUrl) => {
    return fetch(webUrl)
        .then(function(res) {
            return res.json();
        });
}

getNumbers()
    .then(numbersArray => {
        //Supposed to return array of posts title
        return Promise.all(numbersArray.map((number, i) => {
          console.log("Reading number" + number);
          return delay(number, 10000*(i+1));//make a request each ten seconds
        }))
        .then(postTitlesArray => {
            console.log(postTitlesArray)
            });
    })

答案 2 :(得分:0)

你可以使用Promise.all,假设数字不是数千,或者你可以使用批量Promise.all。

然后使用here中的throttlePeriod确保每10秒只发出1个请求。

然后使用特殊值解决失败的请求,这样如果失败,您不会失去所有成功:

var getNumbers = () => {
  return new Promise(function (resolve, reject) {

    console.log("In function getNumbers");
    var data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    resolve(data);
  });
};

function getHtml(webUrl) {
  return fetch(webUrl)
    .then(function (res) {
      return res.json();
    });
}

const Fail = function(reason){this.reason=reason;};
const isFail = x=>(x&&x.constructor)===Fail;
const notFail = x=>!isFail(x);
//maximum 1 per 10 seconds
//you can get throttle period from here:
//https://github.com/amsterdamharu/lib/blob/master/src/index.js
const max1Per10Seconds = lib.throttlePeriod(1,10000)(getHtml);
getNumbers()
.then(
  numbersArray =>
    Promise.all(//process all numbers
      numbersArray
      .map(//map number to url
        number => 
          `https://jsonplaceholder.typicode.com/posts/${number}`
      )
      //map url to promise
      //max1Per10Seconds calls getHtml maximum 1 time per 10 seconds 
      //  (will schedule the calls)
      .map(max1Per10Seconds)
      .map(//map promise to promise that does not reject
        p=>//instead of rejecting promise, resolve with Fail value
          //these Fail values can be filtered out of the result later.
          //(see last then)
          p.catch(err=>new Fail([err,number]))
      )
    )
).then(
  //got the results, not all results may be successes
  postTitlesArray => {
    //is a comment really needed here?
    const successes = postTitlesArray.filter(notFail);
    const failed = postTitlesArray.filter(isFail);
  }
);