ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

ES6 Generator异步生成随机数:蓝桥杯Web应用开发实战解析

ES6 Generator异步生成随机数:蓝桥杯Web应用开发实战解析 上次在群里帮一位准备蓝桥杯Web应用开发的学弟看代码他卡在一道“用生成器异步产出数据”的题上很久。题目本身不算难但他把重点全放在“怎么生成随机数”上忽略了出题人真正想考察的ES6生成器流程控制能力。这其实也是很多备赛同学的共同问题ES6的Generator平时用得少真到比赛里遇到“异步 生成器”的组合容易不知道从哪下手。本文就围绕“用ES6生成器异步产生随机数”这一真题改编方向从生成器基础语法讲起逐步拆解“生成器 Promise”的异步流程控制写法并给出一个可直接运行的Web页面案例。无论你是蓝桥杯Web应用开发的备赛选手还是想补一补ES6异步编程短板的开发者这篇都可以作为一份比较完整的实操笔记。1. 蓝桥杯Web应用开发考点里的ES6生成器1.1 为什么真题会考生成器蓝桥杯Web应用开发方向对JavaScript的考察很少停留在“写个变量、调个函数”的层面更偏向ES6新特性在现代Web项目中的真实用法。从前几年的出题趋势来看Promise、async/await、Generator、模块化、解构赋值、箭头函数这些ES6语法都会出现在不同难度的题目里。生成器和异步扯上关系是因为JavaScript本身是单线程语言异步任务要靠事件循环机制完成。Generator函数虽然不能像async/await那样原生支持异步但它的“暂停”特性非常适合配合Promise做流程控制。蓝桥杯这类考试不愿意考死记硬背而是把“异步产生随机数”这种带有延迟、网络请求特征的小场景包装成题目考察选手对JavaScript异步模型的理解程度。1.2 真题中常见的考核形式如果我们要给“用ES6生成器异步产生随机数”做一个真题模拟大概会是这样一种风格提供一个Web页面要求点击按钮后通过异步任务获取一个随机数这个随机数不能直接同步生成必须经过“异步延迟”或“模拟接口返回”要求使用ES6生成器函数控制异步流程把多个随机数的生成过程串联起来形成一种“异步流水线”的效果。和单纯调用Math.random()不同这道题的核心不是“随机数怎么来”而是“异步流程怎么控制”。这一点要提前想清楚否则很容易答偏。2. 生成器函数基础function* 与 yield2.1 认识Generator函数ES6新增了Generator函数它是一种可以暂停执行和恢复执行的函数。普通函数一旦开始执行就会一直跑到return结束Generator函数则在遇到yield关键字时会主动“让出控制权”暂停在当前位置等待下一次调用next()再继续。定义方式是在函数名前面加一个星号可以写作function* fn()也可以写作function *fn()两种写法都合法。调用Generator函数时函数体不会立刻执行而是返回一个迭代器对象。只有调用这个迭代器的next()方法函数才会真正开始执行。来看一个最简单的例子function* count() { console.log(开始执行); yield 1; console.log(第一次恢复); yield 2; console.log(第二次恢复); return 3; } const iterator count(); console.log(函数调用后尚未执行函数体); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());执行结果如下函数调用后尚未执行函数体 开始执行 { value: 1, done: false } 第一次恢复 { value: 2, done: false } 第二次恢复 { value: 3, done: true }从输出可以看到几个重要特征调用count()时函数体没有执行只返回了迭代器对象每次调用next()函数会执行到下一个yield或return为止next()返回的对象包含value和done两个属性done为false表示还有后续流程done为true表示整个函数执行完毕。2.2 next()传参把数据从外部“喂”进生成器Generator函数不只是简单的暂停还能在恢复时接收外部传入的数据。上一次yield表达式的返回值就是下一次next()传入的参数。这一点是生成器做异步流程控制的关键。function* calc() { const a yield 请输入a; const b yield 请输入b; return a b; } const it calc(); console.log(it.next()); // { value: 请输入a, done: false } console.log(it.next(10)); // { value: 请输入b, done: false } console.log(it.next(20)); // { value: 30, done: true }第一次调用next()时函数执行到第一个yield暂停此时还没有变量接收返回值。第二次调用next(10)时10会作为第一个yield表达式的返回值赋给a然后继续执行到第二个yield暂停。第三次调用next(20)时20赋给b最终返回30。这个“外部向函数内部传值”的能力让Generator函数可以从异步任务中拿到结果再继续执行。比如yield一个Promise等这个Promise异步完成之后通过next(result)把结果传回函数体内部。2.3 yield*在生成器里调用另一个生成器yield*用于在一个Generator函数中委托另一个可迭代对象。它可以理解为“展开”操作把被委托对象产生的所有值逐个产出。function* inner() { yield A; yield B; } function* outer() { yield start; yield* inner(); yield end; } for (const value of outer()) { console.log(value); } // start // A // B // end在真题里yield*出现频率不高但如果你对生成器有一定掌握可以在代码里用它做模块拆分把“随机数生成流程”和“页面渲染流程”分开组织代码结构会更清晰。3. 生成器异步流程控制的核心原理3.1 为什么需要生成器控制异步流程在实际开发中异步操作通常嵌套在回调函数里。一旦要连续发起多个异步请求就会形成“回调地狱”setTimeout(() { console.log(第一步); setTimeout(() { console.log(第二步); setTimeout(() { console.log(第三步); }, 1000); }, 1000); }, 1000);这种代码难以阅读也难以维护。Promise通过.then()链式调用缓解了回调嵌套问题但本质上仍然是“回调的变体”。async/await在ES2017引入用同步写法写异步逻辑解决了大部分问题。而Generator是夹在Promise和async/await之间的过渡方案。它通过yield暂停和next()恢复配合Promise的then回调完全可以实现类似async/await的效果。蓝桥杯真题为了考察ES6的深度有时会刻意要求用Generator来写异步流程。3.2 Generator Promise 的标准套路我们约定这样一个异步任务传入一个数字经过1秒延迟后返回这个数字加1的结果。function asyncAdd(num) { return new Promise(resolve { setTimeout(() { resolve(num 1); }, 1000); }); }用Generator来串联三次asyncAddfunction* task() { const a yield asyncAdd(1); console.log(第一次结果:, a); const b yield asyncAdd(a); console.log(第二次结果:, b); const c yield asyncAdd(b); console.log(第三次结果:, c); return c; } function run(generator) { const iterator generator(); function step(prevValue) { const result iterator.next(prevValue); if (result.done) { return result.value; } // result.value 是一个 Promise Promise.resolve(result.value) .then(value { step(value); }) .catch(err { iterator.throw(err); }); } step(); } run(task);核心逻辑如下调用task()获得迭代器第一次执行iterator.next()函数体运行到第一个yield asyncAdd(1)暂停返回一个Promise我们在step函数里拿到这个Promise调用.then()等待它完成异步完成后把结果作为iterator.next(value)的参数传回去函数从暂停处恢复a就拿到了异步返回的值循环这个过程直到done为true。这个过程是不是很像await事实上async/await底层就可以理解为Generator 自动执行器的语法糖。上面的run函数就是一个手写的最小版“自动执行器”。3.3 手动写一个可复用的run函数为了方便比赛时使用可以把上面的run函数封装成一个通用方法。它能自动驱动一个内部yield Promise的生成器函数function run(generator) { const it generator(); function handle(iteratorResult) { if (iteratorResult.done) { return; } const value iteratorResult.value; if (value instanceof Promise) { value .then(res handle(it.next(res))) .catch(err it.throw(err)); } else { handle(it.next(value)); } } handle(it.next()); }把这个函数放在代码开头后面任何一个需要“生成器 异步”的流程都可以直接使用。4. 真题模拟用ES6生成器异步产生随机数4.1 题目设计与评分点我们把真题模拟成如下需求。题目描述开发一个Web页面页面上有一个按钮。点击按钮后需要按照以下流程执行模拟一次异步接口请求返回一个1到100之间的随机数种子根据第一步的种子再发起一次异步请求生成3个10到99之间的随机整数将最终生成的3个随机数依次渲染到页面列表中整个过程必须由Generator函数控制不能直接使用async/await不能直接在同步代码中调用Math.random()生成最终结果。评分点可以拆成这样是否定义并正确使用Generator函数是否把异步任务封装成Promise;是否理解next()传参能不能把上一次异步结果传到下一次流程页面是否能正常交互并渲染结果是否具备基本的错误处理。注意“能计算出随机数”只是加分项重点考察的是“异步流程控制”。如果你用async/await实现了能跑通但没按要求用Generator在某些严格评分规则下会扣分。4.2 创建项目结构这个案例不需要复杂工程一个HTML文件就可以完成全部演示。为了讲解方便我们创建如下结构generator-random/ └── index.html如果你想在命令行中先测试核心逻辑也可以拆出一个main.js用Node.js运行。本文主要展示Web页面版本。4.3 编写完整HTML页面打开index.html输入以下代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title用ES6生成器异步产生随机数/title style .container { max-width: 600px; margin: 50px auto; padding: 24px; border: 1px solid #e0e0e0; border-radius: 8px; font-family: Microsoft YaHei, sans-serif; } button { padding: 10px 20px; font-size: 16px; background-color: #2f54eb; color: #fff; border: none; border-radius: 4px; cursor: pointer; } button:disabled { background-color: #aaa; cursor: not-allowed; } .list { margin-top: 20px; padding: 0; list-style: none; } .list li { padding: 8px 12px; border: 1px solid #f0f0f0; border-radius: 4px; margin-bottom: 8px; background-color: #fafafa; } /style /head body div classcontainer h2ES6 Generator 异步随机数生成器/h2 p点击按钮后模拟异步接口返回随机数种子再基于种子生成3个随机整数。/p button idgenerateBtn开始异步生成/button ul idresultList classlist/ul p idstatus stylecolor: #666;/p /div script // 模拟异步任务 /** * 模拟异步接口延迟 delay 毫秒后返回数据 */ function mockAsync(data, delay 500) { return new Promise(resolve { setTimeout(() { resolve(data); }, delay); }); } /** * 模拟请求随机数种子 */ function requestSeed() { // 种子范围 1 ~ 100 const seed Math.floor(Math.random() * 100) 1; return mockAsync(seed, 800); } /** * 根据种子生成一个10~99的随机整数 */ function generateNumberBySeed(seed) { // 这里为了演示把 seed 参与计算 const base (seed * 9301 49297) % 233280; const num Math.floor((base / 233280) * 90) 10; return mockAsync(num, 500); } // 生成器流程控制 /** * 核心生成器函数 * 1. 异步获取种子 * 2. 基于种子异步生成3个随机数 * 3. 返回最终数组 */ function* randomFlow() { const seed yield requestSeed(); updateStatus(已获取随机种子: seed); const numbers []; for (let i 0; i 3; i) { const num yield generateNumberBySeed(seed i); numbers.push(num); updateStatus(正在生成第 (i 1) 个随机数...); } return numbers; } /** * 生成器自动执行器 */ function run(generator) { const it generator(); function step(prevValue) { const result it.next(prevValue); if (result.done) { return result.value; } Promise.resolve(result.value) .then(res step(res)) .catch(err { console.error(异步流程出错:, err); it.throw(err); }); } return step(); } // 页面交互 const btn document.getElementById(generateBtn); const list document.getElementById(resultList); const status document.getElementById(status); function updateStatus(text) { status.textContent text; } function renderResult(numbers) { list.innerHTML ; numbers.forEach((num, index) { const li document.createElement(li); li.textContent 随机数 (index 1) num; list.appendChild(li); }); updateStatus(生成完成); } btn.addEventListener(click, function () { // 避免重复点击 btn.disabled true; list.innerHTML ; updateStatus(开始执行生成器流程...); run(randomFlow) .then(numbers { renderResult(numbers); }) .catch(err { updateStatus(生成失败请查看控制台); console.error(err); }) .finally(() { btn.disabled false; }); }); /script /body /html4.4 关键代码解释4.4.1 模拟异步任务mockAsync函数本质上是对setTimeout的Promise封装。真实比赛场景中这一步通常是一个axios请求或fetch()调用但核心思想是一样的yield一个Promise让生成器暂停等异步结果返回后继续执行。requestSeed和generateNumberBySeed都调用了mockAsync保证了“随机数不是同步产生的”而是通过异步延迟返回。这样才符合题目的“异步产生随机数”要求。4.4.2 核心生成器流程const seed yield requestSeed();这一行代码是关键。它做了三件事调用requestSeed()返回一个Promise;yield将这个Promise交给自动执行器生成器暂停等待异步返回自动执行器在resolve后调用next(seed)把种子值传回seed变量。后面的for循环同理。每次迭代都会yield一个generateNumberBySeed(...)产生的Promise拿到结果后push到数组中。整个过程没有回调嵌套代码顺序和同步代码几乎一样。4.4.3 run自动执行器这次与上一节稍有不同run函数返回了step()的返回值而step()在result.done时返回result.value也就是生成器最终return的数组。这样我们就可以在页面中链式调用run(randomFlow).then(numbers { ... });自执行器在这里起到了“内部驱动”的作用所以外层函数里不需要手动调用next()。4.5 运行与预期输出直接用浏览器打开index.html点击“开始异步生成”按钮可以看到状态文字显示“开始执行生成器流程...”大约800毫秒后显示“已获取随机种子: 某个1~100的数”随后每隔500毫秒显示一次“正在生成第N个随机数...”最后列表中出现3个随机数状态变为“生成完成”。控制台没有任何异常页面按钮在流程执行期间处于禁用状态流程结束后恢复可点击。这就是一套完整的“生成器 Promise 页面渲染”的真题模拟实现。4.6 对比async/await写法如果你熟悉async/await会发现生成器版本的流程和下面这段异步写法几乎一一对应async function randomFlowAsync() { const seed await requestSeed(); const numbers []; for (let i 0; i 3; i) { const num await generateNumberBySeed(seed i); numbers.push(num); } return numbers; }对比可以看出对比项Generator 自动执行器async/await暂停语法yieldawait声明方式function*async function返回结果迭代器对象Promise驱动方式需要手动写执行器引擎自动驱动兼容性ES6ES2017代码可读性门槛稍高写法灵活直观、推荐日常使用比赛时如果题目明确要求用生成器就应该写Generator方案如果没要求日常项目开发更推荐async/await。5. 常见问题与排查思路5.1 常见报错与解决思路问题现象常见原因解决思路点击按钮后无任何反应没有调用next()或生成器没有正确启动检查是否通过自执行器或手动it.next()启动流程yield后面的Promise结果拿不到没有把next(res)的返回值传回生成器调用.then(res it.next(res))把结果传回去生成的随机数顺序错乱可能在.then中直接使用外部变量而不是通过next传参把结果统一从next参数传回函数体页面卡死按钮无法再次点击没有处理按钮禁用和恢复逻辑流程结束后通过.finally()恢复按钮it.next()返回{value: undefined}yield后面没有返回值或返回的是非Promise值保证yield表达式返回一个Promise使用for...of遍历生成器时异步操作没有生效for...of只能遍历同步迭代器不能等待Promise完成使用自执行器配合next传参5.2 排查清单如果你在比赛中遇到生成器相关代码运行不符合预期可以按下面顺序排查第一步确认生成器函数声明为function*并且调用时返回的是迭代器第二步确认yield后面跟的是Promise对象而不是一个普通数字第三步确认自执行器中是否调用it.next(prevValue)传回上一次结果第四步确认异步失败时是否调用it.throw(err)否则错误会丢失第五步确认最终结果是通过return返回的而不是只打印到控制台第六步确认页面交互事件里是否处理了按钮防重复点击。按这个顺序排查大多数生成器异步流程问题都能定位。6. 蓝桥杯实战最佳实践6.1 手写自执行器很有必要虽然比赛可以只写Generator函数由自己封装一个run函数来驱动但不要只背代码要理解自执行器的两个核心行为result.done为false时等待yield出的Promise完成完成之后把结果作为next()参数传回生成器。建议在备赛时手写三遍自执行器直到能默写出来。这样即使遇到变种的题目比如yield后面不只有Promise还有普通值也能灵活调整。6.2 异步任务统一封装成Promise在真题模拟中我们用了mockAsync来统一封装异步任务。真实比赛里如果遇到类似“模拟数据请求”“定时器延迟”的场景建议都先封装成Promise函数再放到生成器里yield。这样做有两个好处第一让生成器函数的代码保持干净第二自执行器只需要处理Promise一种情况即可逻辑更简单。6.3 注意变量命名与代码注释蓝桥杯Web应用开发题目的代码量通常不小阅卷时对手写代码的清晰度有一定要求。建议生成器函数统一使用Flow、Task结尾自执行器统一使用run异步任务统一使用request、fetch、load开头这样阅卷人一眼就能理解你的代码结构。yield的含义本身就是“让出控制权”在关键yield行前面加一行注释说明这一行暂停等待什么数据能有效降低后续维护成本。6.4 不要忽略错误处理有些同学写生成器异步流程时只处理了resolve分支没有处理reject分支。真实题目中如果要求“异步获取随机数失败后用兜底值”就必须在自执行器里调用it.throw(error)把异常交回生成器内部处理。在Web页面中还要注意异步流程期间要禁用按钮流程结束无论成功失败都要恢复按钮。否则用户连点多次会出现多个生成器流程同时执行页面状态混乱。6.5 兼容性与运行环境Generator是ES6特性现代浏览器和Node.js 12都支持。如果你在比赛环境中不确定浏览器版本可以先在页面顶部加一段简单的兼容性判断if (typeof function* () {} ! function) { alert(当前浏览器不支持Generator请更换浏览器); }当然蓝桥杯机试环境通常没问题但这个检查在真实项目上线时很有价值。7. 总结本文围绕“用ES6生成器异步产生随机数”这一模拟真题从生成器函数的基础语法讲到了完整的Web页面实现。核心收获有三个第一生成器通过yield暂停函数执行通过next()恢复并传参这种特性天然适合控制异步流程第二生成器本身不会自动等待异步任务必须配合一个自执行器把yield出来的Promise结果通过next传回去才能实现类似await的效果第三涉及“异步产生随机数”这类题考察重点不是随机算法而是异步流程控制能力平时可以把Generator Promise的写法练熟比赛中遇到才能从容应对。下一步你可以尝试把yield后面改成fetch请求模拟一个“从接口获取随机数”的真实场景或者把生成的随机数进一步做可视化展示比如散点图、柱状图。理解了生成器异步控制的核心后这些扩展都不会太难。如果本文对你有帮助建议收藏备用。备赛期间多动手写几遍代码比一直看教程有效得多。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表