您的位置:首页 > 其它

es6 Generator函数的应用

2018-01-30 22:31 525 查看


Generator函数的应用

es6
Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。


异步操作的同步化表达

Generator函数的暂停执行的效果,意味着可以把异步操作写在
yield
表达式里面,等到调用
next
方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在
yield
表达式下面,反正要等到调用
next
方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
function* loadUI() {

showLoadingScreen();

yield loadUIDataAsynchronously();

hideLoadingScreen();

}

var loader = loadUI();

// 加载UI

loader.next()


// 卸载UI

loader.next()


上面代码中,第一次调用
loadUI
函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用
next
方法,则会显示
Loading
界面(
showLoadingScreen
),并且异步加载数据(
loadUIDataAsynchronously
)。等到数据加载完成,再一次使用
next
方法,则会隐藏
Loading
界面。可以看到,这种写法的好处是所有
Loading
界面的逻辑,都被封装在一个函数,按部就班非常清晰。

Ajax 是典型的异步操作,通过 Generator函数部署 Ajax 操作,可以用同步的方式表达。
function* main() {

var result = yield request("http://some.url");

var resp = JSON.parse(result);

console.log(resp.value);

}


function request(url) {

makeAjaxCall(url, function(response){

it.next(response);

});

}


var it = main();

it.next();


上面代码的
main
函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个
yield
,它几乎与同步操作的写法完全一样。注意,
makeAjaxCall
函数中的
next
方法,必须加上
response
参数,因为
yield
表达式,本身是没有值的,总是等于
undefined


下面是另一个例子,通过 Generator函数逐行读取文本文件。
function* numbers() {

let file = new FileReader("numbers.txt");

try {

while(!file.eof) {

yield parseInt(file.readLine(), 10);

}

} finally {

file.close();

}

}


上面代码打开文本文件,使用
yield
表达式可以手动逐行读取文件。


控制流管理

如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
step1(function (value1) {

step2(value1, function(value2) {

step3(value2, function(value3) {

step4(value3, function(value4) {

// Do something with value4

});

});

});

});


采用 Promise 改写上面的代码。
Promise.resolve(step1)

.then(step2)

.then(step3)

.then(step4)

.then(function (value4) {

// Do something with value4

}, function (error) {

// Handle any error from step1 through step4

})

.done();


上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator函数可以进一步改善代码运行流程。
function* longRunningTask(value1) {

try {

var value2 = yield step1(value1);

var value3 = yield step2(value2);

var value4 = yield step3(value3);

var value5 = yield step4(value4);

// Do something with value4

} catch (e) {

// Handle any error from step1 through step4

}

}


然后,使用一个函数,按次序自动执行所有步骤。
scheduler(longRunningTask(initialValue));


function scheduler(task) {

var taskObj = task.next(task.value);

// 如果Generator函数未结束,就继续调用

if (!taskObj.done) {

task.value = taskObj.value

scheduler(task);

}

}


注意,上面这种做法,只适合同步操作,即所有的
task
都必须是同步的,不能有异步操作。因为这里的代码一得到返回值,就继续往下执行,没有判断异步操作何时完成。如果要控制异步的操作流程,详见后面的《异步操作》一章。

下面,利用
for...of
循环会自动依次执行
yield
命令的特性,提供一种更一般的控制流管理的方法。
let steps = [step1Func, step2Func, step3Func];


function *iterateSteps(steps){

for (var i=0; i< steps.length; i++){

var step = steps[i];

yield step();

}

}


上面代码中,数组
steps
封装了一个任务的多个步骤,Generator函数
iterateSteps
则是依次为这些步骤加上
yield
命令。

将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。
let jobs = [job1, job2, job3];


function* iterateJobs(jobs){

for (var i=0; i< jobs.length; i++){

var job = jobs[i];

yield* iterateSteps(job.steps);

}

}


上面代码中,数组
jobs
封装了一个项目的多个任务,Generator函数
iterateJobs
则是依次为这些任务加上
yield*
命令。

最后,就可以用
for...of
循环一次性依次执行所有任务的所有步骤。
for (var step of iterateJobs(jobs)){

console.log(step.id);

}


再次提醒,上面的做法只能用于所有步骤都是同步操作的情况,不能有异步操作的步骤。如果想要依次执行异步的步骤,必须使用后面的《异步操作》一章介绍的方法。

for...of
的本质是一个
while
循环,所以上面的代码实质上执行的是下面的逻辑。
var it = iterateJobs(jobs);

var res = it.next();


while (!res.done){

var result = res.value;

// ...

res = it.next();

}



部署 Iterator 接口

利用 Generator函数,可以在任意对象上部署 Iterator 接口。
function* iterEntries(obj) {

let keys = Object.keys(obj);

for (let i=0; i < keys.length; i++) {

let key = keys[i];

yield [key, obj[key]];

}

}


let myObj = { foo: 3, bar: 7 };


for (let [key, value] of iterEntries(myObj)) {

console.log(key, value);

}


// foo 3

// bar 7


上述代码中,
myObj
是一个普通对象,通过
iterEntries
函数,就有了
Iterator 接口。也就是说,可以在任意对象上部署
next
方法。

下面是一个对数组部署 Iterator 接口的例子,尽管数组原生具有这个接口。
function* makeSimpleGenerator(array){

var nextIndex = 0;


while(nextIndex < array.length){

yield array[nextIndex++];

}

}


var gen = makeSimpleGenerator(['yo', 'ya']);


gen.next().value // 'yo'

gen.next().value // 'ya'

gen.next().done  // true



作为数据结构

Generator 可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。
function *doStuff() {

yield fs.readFile.bind(null, 'hello.txt');

yield fs.readFile.bind(null, 'world.txt');

yield fs.readFile.bind(null, 'and-such.txt');

}


上面代码就是依次返回三个函数,但是由于使用了 Generator函数,导致可以像处理数组那样,处理这三个返回的函数。
for (task of doStuff()) {

// task是一个函数,可以像回调函数那样使用它

}


实际上,如果用 ES5 表达,完全可以用数组模拟 Generator 的这种用法。
function doStuff() {

return [

fs.readFile.bind(null, 'hello.txt'),

fs.readFile.bind(null, 'world.txt'),

fs.readFile.bind(null, 'and-such.txt')

];

}


上面的函数,可以用一模一样的
for...of
循环处理!两相一比较,就不难看出 Generator 使得数据或者操作,具备了类似数组的接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: