簡體   English   中英

承諾不返回期望值

[英]Promise not returning expected value

我一直在學習諾言,我有一個問題。 我有一個名為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();
        });
}

您的方法在完成所需的工作時會涉及一些概念性的事情。

首先, .map()是同步的。 這意味着它可以運行完成,而無需等待任何異步操作完成。

其次, setTimeout()是非阻塞的。 它只是將計時器安排在將來的某個時間,然后您的.map()回調立即返回,不返回任何內容。

因此,您的方法根本行不通。

從您的評論看來,您要完成的工作似乎是在循環中進行一堆網絡調用,但是要在它們之間設置延遲,以免造成速率限制。 有很多方法可以做到這一點。

要使此工作有效,有兩個基本概念:

  1. 使您的異步操作具有順序性,以便在上一個操作完成之前不會啟動下一個操作。

  2. 在開始下一個承諾之前,要對保證做出延遲。

首先,我將展示一種使用async/await的ES7方法,因為從概念async/await ,它可能是最簡單的。

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

您可以使用Promise.all(假設數量不成千上萬),否則可以使用批處理的Promise.all。

然后從此處使用節流閥,以確保每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);
  }
);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM