javascript中的yield

概述

yield 关键字用来暂停和恢复一个生成器函数(function* 或遗留的生成器函数),可以理解为普通函数的return关键字。yield实际上返回的是一个InteratorResult对象,他有value和done两个属性,分别代表返回值和是否完成。

语法

定义

function* createGenerator(){
    yield 1;
    yield 2;
    yield 3;
}
// or
var createGenerator = function*(){
    ...
};

使用

for(const p of createGenerator()){
    console.log(p);
}
// or
let a = createGenerator();
console.log(a.next());
// {value:1,done:false}
console.log(a.next());
// {value:2,done:false}
console.log(a.next());
// {value:3,done:false}
console.log(a.next());
// {value:undefined,done:true}

function*前缀定义的函数为生成器函数,其中使用的yield返回的数据为生成器每次迭代返回的数据。生成器不存在箭头函数的写法。

yield与递归的配合使用

也是想到以前使用递归检索文件,所以想到了用yield做一个生成器,让后续操作更直观。
假设有以下的文件层次结构:

├── a1
├── dir1
├── dir2
├── dir3
│   └── a2
├── dir4
├── dir5
│   └── a3
├── dir6
├── dir7
├── dir8
│   └── dir10
└── dir9
    └── dir11
        └── a4

其中dir开头的为文件夹,a开头的为文件,需要检索所有的文件,就可以使用递归生成器。

import fs from 'fs';
import path from 'path';
var recursiveDirectory = function*(dirPath){
    for (const fileName of fs.readdirSync(dirPath)) {
        let tempPath = path.join(dirPath,fileName);
        let stat = fs.statSync(tempPath);
        if(stat.isDirectory()){
            for(const fileName2 of recursiveDirectory(tempPath)){
                yield fileName2;
            }
        }else if(stat.isFile()){
            yield fileName;
        }
    }
}

var target = '/path/of/dir';
for(const c of recursiveDirectory(target)){
    console.log(c);
}

输出结果:

a1
a2
a3
a4

递归生成器可能看上去逻辑还有些绕,但是下方的遍历操作看着就直观多了,这样操作不仅分割的检索操作和后续逻辑代码,而且生成器的操作属于lazyload,减少资源浪费。

最近又看到一个异步版本:

async function* walk(dir) {
    for await (const d of await fs.promises.opendir(dir)) {
        const entry = path.join(dir, d.name);
        if (d.isDirectory()) yield* await walk(entry);
        else if (d.isFile()) yield entry;
    }
}
(async () => {
  for await (const p of walk('/path/of/dir'))
    console.log(p)
})();

这段异步的代码真的很美丽!!!(没有第三方模块引用,异步遍历)

总结

有关yield关键字的研究还有,包括迭代器的return、throw、next函数,后续再做补全。