关于promise的两个问题
发布于 9 年前 作者 postgetme 6900 次浏览 最后一次编辑是 8 年前 来自 问答

1.为什么promise不能在forEach中执行,只能在for循环中执行呢?

async function test() {
	//这样不行
	arr.forEach(item => {
	  await promiseFunc(item)
	})
	
	//这样可以
	for (var i = 0; i < arr.length; i++) {
		await promiseFunc(arr[i])
	}
}

2.promise.map处理很大的数组(比如10000个元素)会导致内存占用很大吗?

async function test() {
	//arr有10000个元素,这样是不是会占用10000个单位的内存呢?
	await Promise.map(arr, promiseFunc)
	
	//arr有10000个元素,加上 {concurrency: 5}参数,这样可不可以把内存占用降到5个单位呢?
	await Promise.map(arr, promiseFunc, {concurrency: 5})
}
12 回复

for的时候将异步动作丢到数组里,等到for完成后,用promise的方法处理数组啊

重复异步操作,建议用EventProxy.

ep.after("afterFiles",files.length,function( list ){
		ep.emit("success",list);
});
files.forEach(function( value,index,arr ){
				var temp = path + "\\" + value;
				fs.stat(temp,ep.group('afterFiles',function( stat ){
					if( stat.isFile() ) { //是文件
						return { filename:temp,type:1 }; //1代表文件
					} else if( stat.isDirectory() ) { //是文件夹
						return { filename:temp,type:2 }; //2代表文件
					}
				}));
});

这是group的源码

EventProxy.prototype.group = function (eventname, callback) {
    var that = this;
    var group = eventname + '_group';
    var index = that._after[group].index;
    that._after[group].index++;
    return function (err, data) {
      if (err) {
        // put all arguments to the error handler
        return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
      }
      that.emit(group, {
        index: index,
        // callback(err, args1, args2, ...)
        result: callback ? callback.apply(null, SLICE.call(arguments, 1)) : data
      });
    };
  };```
  只是做计数,在大循环中肯定比对象快。
arr.forEach(async function(item){
  await promiseFunc(item)
})

其实这种情况还是用Promise.all吧,你这种写会一个接一个地发送请求,很慢的

第一个问题:为什么不行? 因为 await 只能在有 async 的函数中运行,匿名函数没有 async,所以不行。

如果想并发的执行,可以这样写:

arr.forEach(async function(item){
  await promiseFunc(item)
})

如果你不想并发执行,想顺序执行,就使用你写的 for循环就可以了。

这个部分你可以参考阮一峰老师的这篇文章,我也是从他那里学到的。

第二个问题:要看语义是什么,在 js 标准里面我没有找到 Promise.map MDN,那我只好假定你使用的是 bluebird。

BuildBird中的map方法中的参数可以指定并发数,这样一次就可以执行多个promise。

但是,你问的是参数省内存,我认为参数已经占用了内存没有办法省了。

于是我猜想你的问题是:计算过程中省内存

更具体地表达是:在指定{concurrency: 5} 参数后,是否能省计算中的内存?

从这个问题来看,你不大确定,指定了concurrency后,函数的运算情况:第一种可能: 在运算过程是否是一次性计算所有的值,然后等;第二种可能:还是一次计算5次,等5次计算完成后,再计算5次,一直这样计算完成。

参考 bluebird 的文档 promise.map,我倾向认为是第二种:当设置了并发值后,每次只有部分值参与计算,这样相当于省了函数计算的内存。

所以第二个问题的答案是:省内存,最多只有5个单位

@i5ting 桑老师说的是并发执行吧,我这里用for和forEach是想试试串行执行的效果。

@p2227 是的,所以我改并发,于是就有了第2个问题。

@htoooth 多谢! 第一个问题:确实是这个道理。 第二个问题:我是用的bluebird。按你讲的,这两种写法,参数(10000个参数 / 10000个promise对象)和结果(10000个结果,我这个promiseFunc是有返回值的)占用的内存是一样的,只是在计算时能省一点内存。如果是这样的话,那么第二种写法并不会产生省内存的明显效果了。

@zouzhenxing 例子木有看懂哦,碰到apply、call我就蒙了。感觉用事件还是没有promise直观啊。

@postgetme 下面这段是摘至 EventProxy Readme.md

重复异步协作

此处以读取目录下的所有文件为例,在异步操作中,我们需要在所有异步调用结束后,执行某些操作。

var ep = new EventProxy();
ep.after('got_file', files.length, function (list) {
  // 在所有文件的异步执行结束后将被执行
  // 所有文件的内容都存在list数组中
});
for (var i = 0; i < files.length; i++) {
  fs.readFile(files[i], 'utf-8', function (err, content) {
    // 触发结果事件
    ep.emit('got_file', content);
  });
}

after方法适合重复的操作,比如读取10个文件,调用5次数据库等。将handler注册到N次相同事件的触发上。达到指定的触发数,handler将会被调用执行,每次触发的数据,将会按触发顺序,存为数组作为参数传入。

@zouzhenxing 明白了。用”回调+事件“还是用promise还是看个人喜好。现在不是流行用promise嘛。

你这个代码改成promise的写法是:

function readFileAsync(file) {
  return new Promise((resolve, reject) => {
    fs.readFile(file, 'utf-8', (err, content) => {
      if (err) {
        reject(err)
      } else {
        resolve(content)
      }
    })
  })
}

Promise.all(files.map(readFileAsync)).then(list => {
  
})

@postgetme 明白的,我只是说一下我的思路。 我没有看过Promise.all的源码,如果是那个循环很大的情况下,也就是文件很多的情况下。 EventProxy只是进行了计数,没有新建对象,我个人觉得应该会快一些。 呵呵请指教

@zouzhenxing promise的原理我一点也不懂哦,效率应该是没有EventProxy高。

回到顶部