[英]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()
回調立即返回,不返回任何內容。
因此,您的方法根本行不通。
從您的評論看來,您要完成的工作似乎是在循環中進行一堆網絡調用,但是要在它們之間設置延遲,以免造成速率限制。 有很多方法可以做到這一點。
要使此工作有效,有兩個基本概念:
使您的異步操作具有順序性,以便在上一個操作完成之前不會啟動下一個操作。
在開始下一個承諾之前,要對保證做出延遲。
首先,我將展示一種使用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.