我一直在学习承诺,我有一个问题。我有一个名为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();
});
}
答案 0 :(得分:2)
你的方法做你想做的事情有几个概念性的事情。
首先,.map()
是同步的。这意味着它将运行完成,并且不会等待任何异步操作完成。
其次,setTimeout()
是非阻塞的。它只是在将来的某个时间安排一个计时器,然后你的.map()
回调立即返回,什么都不返回。
所以,你的方法根本不起作用。
根据您的评论,您尝试完成的工作似乎是在一个循环中进行一系列网络呼叫,但在它们之间设置延迟,这样您就不会受到速率限制。有很多方法可以做到这一点。
您需要使用两个基本概念:
使您的异步操作顺序进行,以便下一个操作无法启动,直到完成前一个操作。
在开始下一个延迟之前设置一个与promises一起使用的延迟。
我首先使用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);
}
);