使用request模块时Generator函数的yield为何不生效?
问题出在哪?
你这段代码的核心问题有两个:
- Generator函数不会自己跑起来:调用
fetch(obj)只是得到了一个迭代器对象,函数体里的代码(打印2、请求数据这些)根本没执行——必须手动调用迭代器的next()方法,才能推进Generator的执行流程。 - forEach做不到串行异步处理:forEach是同步遍历,它会瞬间创建所有Generator对象,但不会等待每个请求完成,完全不符合你“处理完当前URL再拿下一个”的需求。
怎么解决?
Bluebird本身就有工具帮你搞定Generator的自动执行,再配合它的串行遍历方法,就能完美实现你的需求。看重构后的代码:
var Promise = require("bluebird"); var request = Promise.promisify(require("request"), { multiArgs: true }); Promise.promisifyAll(request, { multiArgs: true }); var url_arr = [ "https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=AIzaSyDe3MyoHI6aSbYKdHOXloz9QepAMfes9XE", "https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=AIzaSyDe3MyoHI6aSbYKdHOXloz9QepAMfes9XE" ]; // 用Promise.coroutine包装Generator,让它自动执行 const fetchUrl = Promise.coroutine(function* (obj) { console.log("2"); // yield会自动等待请求完成,直接拿到结果 const myobj = yield request.getAsync(obj); console.log("3", myobj); return myobj; }); // 用Promise.each串行处理数组,前一个请求完再处理下一个 Promise.each(url_arr, (obj) => { console.log("1", obj); return fetchUrl(obj); }) .then((allResults) => { console.log("所有请求都完成啦", allResults); }) .catch((err) => { console.error("某个请求出错了", err); });
为什么这么改?
- Promise.coroutine:这个工具会把你的Generator函数转换成一个返回Promise的函数,内部自动帮你调用迭代器的
next(),直到Generator执行完毕。而且yield后面的Promise完成后,会自动把结果赋值给前面的变量,不用手动处理promise的then。 - Promise.each:Bluebird的这个方法专门用来做串行异步遍历——它会等当前元素的处理函数(这里是
fetchUrl(obj))完成后,再处理下一个元素,完全满足你“先获取当前URL数据,再处理下一个”的要求。 - 另外,你原来写的
request.getAsync(obj).promise()可以简化成request.getAsync(obj),因为promisifyAll已经给request添加了带Async后缀的异步方法,这些方法直接返回Promise。
现在运行这段代码,控制台会按1 -> 2 -> 3 -> 1 -> 2 -> 3的顺序打印,完全符合你的预期。
内容的提问来源于stack exchange,提问作者Amulya Kashyap
相关产品推荐
相关产品推荐

