ES6 的“奇葩”执行,yield 相关
发布于 4 年前 作者 g8up 4852 次浏览 来自 问答

工程(Koa、koa-router、sequelizejs、sqlite3)里有这么一段函数作为路由的 handler。 向 ret 对象里插入新的属性,循环插入的过程可以看到正常执行,但循环之后返回的结果却是 {}。

"use strict"
var Host = require('../model/Host.js');

module.exports = {
	getList: function* ({
		platform = 'audit'
	}) {
		var hosts = yield Host.findAll();// 通过 sequelize 模块读取数据库
		var ret = {};
		hosts.forEach( host => { // ? 难道这里有异步?
			host = host.dataValues;
			let owner = host.owner;
			if (!ret[owner]) {
				ret[owner] = [];
			}
			ret[owner].push( host );
			console.log( ret[owner] );// 此处可以正常打印出 push 进去的 对象
		});
		console.log('ret', ret )// {} 这里竟然为空(如果放到 setTimeout 里,输出才符合预期)
		return ret;
	}
}
18 回复

forEach没有异步。。。。是自己数据问题吧

大神,这是ES6语法来的。箭头函数。

hosts可能不是个raw Array,可能是一个具有类似array接口的东西,forEach实际是异步执行的实现。上面都是我猜的。

@valaxy 我也觉得诡异,直觉是与 yield 有关,我对 generator 不太熟悉。

@anotherWill 在箭头函数内访问 ret 变量符合预期,在循环之后 ret 就是 {}。但是加了 setTimeout 又符合预期。

@g8up es6的作用域不同吧。es6有块级作用域。

去除 getList 的参数后,一切平静了。

那他们的打印顺序呢?

如果 console.log(ret) 在后面的话,没理由里面没东西啊。

sequelize 不可能去替代 foreach 函数的我觉得。 @magicdawn @valaxy

yield Host.findAll();/ 这个方法里面你有封装成Promise吗

@c15881291595 sequelize的查询方法返回的都是Promise

foreach里面的回调不是立即执行,但下面的console.log是立即执行的,那么输出的结果就是尚未被处理的,如果你把foreach改成for循环就能得到你想要的结果

又发现,将 forEach 抽离成函数来调用,一切就能正常:

function groupByOwner(hosts) {
	var result = {};
	hosts.forEach(host => {
		host = host.dataValues;
		var owner = host.owner;
		if (!result[owner]) {
			result[owner] = [];
		}
		result[owner].push(host);
	});
	return result;
}

module.exports = {
	get: function* ({
		platform = 'audit'
	}) {
		var hosts = yield Host.findAll({
			attributes: ['host', 'port', 'root', 'owner', 'platform']
		});
		return groupByOwner(hosts);
	}
}

forEach(function(){}.bind(this));试试这样。

需要理解generator和promise。 genrator结合promise可以同步书写异步代码。

yield是generator的语法写法。 可以yield一个promise,也可以yield一个立即数。 如果要实现异步编程,是通过yield一个promise实现的 如果yield一个立即数,需要setTimeout(0) 要切断上下文

@g8up 名字很屌啊,之前遇到过类似,写了下心得,async await与yield的混合使用

我测试的是没有问题啊,测试代码如下

'use strict';
const co = require('co');
//虚假构造了一个findAll函数,反正只要返回的是promise就行了
function findAll() {
	return new Promise(function (resolve, reject) {
    	let arr = [];
    	for (let i = 0; i < 2; i++) {
        	let tmp = {
            	dataValues: {
                	owner: i + ': hyj'
            	}
        	};
        	arr.push(tmp);
    	}
    	resolve(arr);
	});
}
//此处基copy楼主的代码
const obj = {
	getList: function * () {
    	var hosts = yield findAll();// 通过 sequelize 模块读取数据库
    	var ret = {};
    	hosts.forEach(host => { // ? 难道这里有异步?
        	host = host.dataValues;
        	let owner = host.owner;
			
        	if (!ret[owner]) {
            	ret[owner] = [];
        	}
        	ret[owner].push(host);
        	console.log(ret[owner]);// 此处可以正常打印出 push 进去的 对象
    	});
		//此处输出正常,为:ret { '0: hyj': [ { owner: '0: hyj' } ], '1: hyj': [ { owner: '1: hyj' } ] }
    	console.log('ret', ret);
    	return ret;
	}
};
//使用co执行,结果正常
co(obj.getList).catch(err=>console.error(err));
//把obj函数放到另一个js文件里module.exports出来,使用co执行,结果同样正常
//co(require('./tetsYiled.js').getList).catch(err=>console.error(err));
回到顶部