javascript - promise 未返回预期值

标签 javascript node.js

我一直在学习 Promise,但我有一个问题。我有一个名为 getNumber 的函数,它返回一个数字数组(为了理解)。我使用该函数迭代该数组并对每个值发出 http 请求(使用 setTimeout 在调用之间进行延迟)

然后我想使用在 then 函数中收集的信息,但它给了我一个“未定义错误”。显然这里有问题,但我看不到它。你知道我该如何解决这个问题以及出了什么问题吗?

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

最佳答案

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

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

其次,setTimeout() 是非阻塞的。它只是在未来的某个时间安排一个计时器,然后您的 .map() 回调立即返回,不返回任何内容。

所以,你的方法根本行不通。

从您的评论来看,您想要完成的任务似乎是在循环中进行一堆网络调用,但在它们之间设置延迟,这样您就不会受到速率限制。有很多方法可以做到这一点。

要实现这一点,您需要两个基本概念:

  1. 使异步操作按顺序进行,以便在前一个操作完成之前不会启动下一个操作。

  2. 在开始下一个 promise 之前,设置一个适合 promise 的延迟。

我将首先展示使用 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 时使用)。所有选项都是可选的。

关于javascript - promise 未返回预期值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48255970/

相关文章:

javascript - 基于标记的 Google map 中心

javascript - HttpRequestHandler : Sending a pdf file in response

用于检测特定更新面板的 AsyncPostback 的开始和结束的 Javascript

javascript - Webpack - 当导入未定义/未声明时发出警告

javascript - 获得两者 - 下载链接和链接以使用 aws sdk 从 s3 存储桶在浏览器中查看

javascript - 通过管道而不是 websocket 连接 Puppeteer 的优点和缺点是什么

javascript - 如何使用淡入淡出更改背景图像

javascript - 如何添加动态包装任何函数调用的 JS 代码?

Node.js - 使用 'request' 和 'node-canvas' 下载并处理图像

javascript - Puppeteer - 如何获取当前页面(应用程序/pdf)作为缓冲区或文件?