登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
3
Star
47
Fork
21
DreamCoders
/
CoderGuide
代码
Issues
1169
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
async、await 实现原理
待办的
#IAG9P9
陌生人
拥有者
创建于
2024-07-29 16:07
<h1>JavaScript 异步编程回顾</h1><p>由于 JavaScript 是单线程执行模型,因此必须支持异步编程才能提高运行效率。异步编程的语法目标是让异步过程写起来像同步过程。</p><h2>1. 回调函数</h2><p>回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。</p><pre><code class="language-js">const fs = require('fs') fs.readFile('/etc/passwd', (err, data) => { if (err) { console.error(err) return } console.log(data.toString()) })</code></pre><p>回调函数最大的问题是容易形成回调地狱,即多个回调函数嵌套,降低代码可读性,增加逻辑的复杂性,容易出错。</p><pre><code class="language-js">fs.readFile(fileA, function (err, data) { fs.readFile(fileB, function (err, data) { // ... }) })</code></pre><h2>2. Promise</h2><p>为解决回调函数的不足,社区创造出 Promise。</p><pre><code class="language-js">const fs = require('fs') const readFileWithPromise = file => { return new Promise((resolve, reject) => { fs.readFile(file, (err, data) => { if (err) { reject(err) } else { resolve(data) } }) }) } readFileWithPromise('/etc/passwd') .then(data => { console.log(data.toString()) return readFileWithPromise('/etc/profile') }) .then(data => { console.log(data.toString()) }) .catch(err => { console.log(err) })</code></pre><p>简单的 Promise 实现,窥探下本质</p><p>Promise 实际上是利用编程技巧将回调函数的横向加载,改成纵向加载,达到链式调用的效果,避免回调地狱。最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。</p><h2>3. async、await</h2><p>为了解决 Promise 的问题,async、await 在 ES8 中被提了出来,是目前为止最好的解决方案</p><pre><code class="language-js">const fs = require('fs') async function readFile() { try { var f1 = await readFileWithPromise('/etc/passwd') console.log(f1.toString()) var f2 = await readFileWithPromise('/etc/profile') console.log(f2.toString()) } catch (err) { console.log(err) } }\</code></pre><p>async、await 函数写起来跟同步函数一样,条件是需要接收 Promise 或原始类型的值。异步编程的最终目标是转换成人类最容易理解的形式。</p><h1>async、await</h1><p>分析 async、await 实现原理之前,先介绍下预备知识</p><h2>1. generator</h2><p>generator 函数是协程在 ES6 的实现。协程简单来说就是多个线程互相协作,完成异步任务。</p><p>整个 generator 函数就是一个封装的异步任务,异步操作需要暂停的地方,都用 yield 语句注明。generator 函数的执行方法如下:</p><pre><code class="language-js">function* gen(x) { console.log('start') const y = yield x * 2 return y } const g = gen(1) g.next() // start { value: 2, done: false } g.next(4) // { value: 4, done: true }</code></pre><ul><li><code>gen()</code> 不会立即执行,而是一上来就暂停,返回一个 <code>Iterator</code> 对象(具体可以参考 <a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fwangfupeng1988%2Fjs-async-tutorial%2Fblob%2Fmaster%2Fpart4-generator%2F02-iterator.md" target="">Iterator遍历器</a> )</li><li>每次 <code>g.next()</code> 都会打破暂停状态去执行,直到遇到下一个 <code>yield</code> 或者 <code>return</code></li><li>遇到 <code>yield</code> 时,会执行 <code>yield</code> 后面的表达式,并返回执行之后的值,然后再次进入暂停状态,此时 <code>done: false</code> 。</li><li><code>next</code> 函数可以接受参数,作为上个阶段异步任务的返回结果,被函数体内的变量接收</li><li>遇到 <code>return</code> 时,会返回值,执行结束,即 <code>done: true</code></li><li>每次 <code>g.next()</code> 的返回值永远都是 <code>{value: ... , done: ...}</code> 的形式</li></ul><h2>2. thunk函数</h2><p>JavaScript 中的 thunk 函数(译为转换程序)简单来说就是把带有回调函数的多参数函数转换成只接收回调函数的单参数版本</p><pre><code class="language-js">const fs = require('fs') const thunkify = fn => (...rest) => callback => fn(...rest, callback) const thunk = thunkify(fs.readFile) const readFileThunk = thunk('/etc/passwd', 'utf8') readFileThunk((err, data) => { // ... })</code></pre><p>单纯的 thunk 函数并没有很大的用处, 大牛们想到了和 generator 结合:</p><pre><code class="language-js">function* readFileThunkWithGen() { try { const content1 = yield readFileThunk('/etc/passwd', 'utf8') console.log(content1) const content2 = yield readFileThunk('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { console.error(err) return 'fail' } } const g = readFileThunkWithGen() g.next().value((err, data) => { if (err) { return g.throw(err).value } g.next(data.toString()).value((err, data) => { if (err) { return g.throw(err).value } g.next(data.toString()) }) })</code></pre><p>thunk 函数的真正作用是统一多参数函数的调用方式,在 next 调用时把控制权交还给 generator,使 generator 函数可以使用递归方式自启动流程</p><pre><code class="language-js">const run = generator => { const g = generator() const next = (err, ...rest) => { if (err) { return g.throw(err).value } const result = g.next(rest.length > 1 ? rest : rest[0]) if (result.done) { return result.value } result.value(next) } next() } run(readFileThunkWithGen)</code></pre><p>有了自启动的加持之后,generator 函数内就可以写"同步"的代码了。generator 函数也可以与 Promise 结合:</p><pre><code class="language-js">function* readFileWithGen() { try { const content1 = yield readFileWithPromise('/etc/passwd', 'utf8') console.log(content1) const content2 = yield readFileWithPromise('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { console.error(err) return 'fail' } } const run = generator => { return new Promise((resolve, reject) => { const g = generator() const next = res => { const result = g.next(res) if (result.done) { return resolve(result.value) } result.value .then( next, err => reject(gen.throw(err).value) ) } next() }) } run(readFileWithGen) .then(res => console.log(res)) .catch(err => console.log(err))</code></pre><p>generator 可以暂停执行,很容易让它和异步操作产生联系,因为我们在处理异步操作时,在等待的时候可以暂停当前任务,把程序控制权交还给其他程序,当异步任务有返回时,在回调中再把控制权交还给之前的任务。generator 实际上并没有改变 JavaScript 单线程、使用回调处理异步任务的本质。</p><h2>3. co 函数库</h2><p>每次执行 generator 函数时自己写启动器比较麻烦。 <a href="https://github.com/tj/co" target="">co函数库</a> 是一个 generator 函数的自启动执行器,使用条件是 generator 函数的 yield 命令后面,只能是 thunk 函数或 Promise 对象,co 函数执行完返回一个 Promise 对象。</p><pre><code class="language-js">const co = require('co') co(readFileWithGen).then(res => console.log(res)) // 'done' co(readFileThunkWithGen).then(res => console.log(res)) // 'done'</code></pre><p>co 函数库的源码实现其实就是把上面两种情况做了综合:</p><pre><code class="language-js">// 做了简化,与源码基本一致 const co = (generator, ...rest) => { const ctx = this return new Promise((resolve, reject) => { const gen = generator.call(ctx, ...rest) if (!gen || typeof gen.next !== 'function') { return resolve(gen) } const onFulfilled = res => { let ret try { ret = gen.next(res) } catch (e) { return reject(e) } next(ret) } const onRejected = err => { let ret try { ret = gen.throw(err) } catch (e) { return reject(e) } next(ret) } const next = result => { if (result.done) { return resolve(result.value) } toPromise(result.value).then(onFulfilled, onRejected) } onFulfilled() }) } const toPromise = value => { if (isPromise(value)) return value if ('function' == typeof value) { return new Promise((resolve, reject) => { value((err, ...rest) => { if (err) { return reject(err) } resolve(rest.length > 1 ? rest : rest[0]) }) }) } } </code></pre><h2>4. 理解 async、await</h2><p>一句话,async、await 是 co 库的官方实现。也可以看作自带启动器的 generator 函数的语法糖。不同的是,async、await 只支持 Promise 和原始类型的值,不支持 thunk 函数。</p><pre><code class="language-js">// generator with co co(function* () { try { const content1 = yield readFileWithPromise('/etc/passwd', 'utf8') console.log(content1) const content2 = yield readFileWithPromise('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { console.error(err) return 'fail' } }) // async await async function readfile() { try { const content1 = await readFileWithPromise('/etc/passwd', 'utf8') console.log(content1) const content2 = await readFileWithPromise('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { throw(err) } } readfile().then( res => console.log(res), err => console.error(err) )</code></pre><h1>总结</h1><p>不论以上哪种方式,都没有改变 JavaScript 单线程、使用回调处理异步任务的本质。人类总是追求最简单易于理解的编程方式。</p>
<h1>JavaScript 异步编程回顾</h1><p>由于 JavaScript 是单线程执行模型,因此必须支持异步编程才能提高运行效率。异步编程的语法目标是让异步过程写起来像同步过程。</p><h2>1. 回调函数</h2><p>回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。</p><pre><code class="language-js">const fs = require('fs') fs.readFile('/etc/passwd', (err, data) => { if (err) { console.error(err) return } console.log(data.toString()) })</code></pre><p>回调函数最大的问题是容易形成回调地狱,即多个回调函数嵌套,降低代码可读性,增加逻辑的复杂性,容易出错。</p><pre><code class="language-js">fs.readFile(fileA, function (err, data) { fs.readFile(fileB, function (err, data) { // ... }) })</code></pre><h2>2. Promise</h2><p>为解决回调函数的不足,社区创造出 Promise。</p><pre><code class="language-js">const fs = require('fs') const readFileWithPromise = file => { return new Promise((resolve, reject) => { fs.readFile(file, (err, data) => { if (err) { reject(err) } else { resolve(data) } }) }) } readFileWithPromise('/etc/passwd') .then(data => { console.log(data.toString()) return readFileWithPromise('/etc/profile') }) .then(data => { console.log(data.toString()) }) .catch(err => { console.log(err) })</code></pre><p>简单的 Promise 实现,窥探下本质</p><p>Promise 实际上是利用编程技巧将回调函数的横向加载,改成纵向加载,达到链式调用的效果,避免回调地狱。最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。</p><h2>3. async、await</h2><p>为了解决 Promise 的问题,async、await 在 ES8 中被提了出来,是目前为止最好的解决方案</p><pre><code class="language-js">const fs = require('fs') async function readFile() { try { var f1 = await readFileWithPromise('/etc/passwd') console.log(f1.toString()) var f2 = await readFileWithPromise('/etc/profile') console.log(f2.toString()) } catch (err) { console.log(err) } }\</code></pre><p>async、await 函数写起来跟同步函数一样,条件是需要接收 Promise 或原始类型的值。异步编程的最终目标是转换成人类最容易理解的形式。</p><h1>async、await</h1><p>分析 async、await 实现原理之前,先介绍下预备知识</p><h2>1. generator</h2><p>generator 函数是协程在 ES6 的实现。协程简单来说就是多个线程互相协作,完成异步任务。</p><p>整个 generator 函数就是一个封装的异步任务,异步操作需要暂停的地方,都用 yield 语句注明。generator 函数的执行方法如下:</p><pre><code class="language-js">function* gen(x) { console.log('start') const y = yield x * 2 return y } const g = gen(1) g.next() // start { value: 2, done: false } g.next(4) // { value: 4, done: true }</code></pre><ul><li><code>gen()</code> 不会立即执行,而是一上来就暂停,返回一个 <code>Iterator</code> 对象(具体可以参考 <a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fwangfupeng1988%2Fjs-async-tutorial%2Fblob%2Fmaster%2Fpart4-generator%2F02-iterator.md" target="">Iterator遍历器</a> )</li><li>每次 <code>g.next()</code> 都会打破暂停状态去执行,直到遇到下一个 <code>yield</code> 或者 <code>return</code></li><li>遇到 <code>yield</code> 时,会执行 <code>yield</code> 后面的表达式,并返回执行之后的值,然后再次进入暂停状态,此时 <code>done: false</code> 。</li><li><code>next</code> 函数可以接受参数,作为上个阶段异步任务的返回结果,被函数体内的变量接收</li><li>遇到 <code>return</code> 时,会返回值,执行结束,即 <code>done: true</code></li><li>每次 <code>g.next()</code> 的返回值永远都是 <code>{value: ... , done: ...}</code> 的形式</li></ul><h2>2. thunk函数</h2><p>JavaScript 中的 thunk 函数(译为转换程序)简单来说就是把带有回调函数的多参数函数转换成只接收回调函数的单参数版本</p><pre><code class="language-js">const fs = require('fs') const thunkify = fn => (...rest) => callback => fn(...rest, callback) const thunk = thunkify(fs.readFile) const readFileThunk = thunk('/etc/passwd', 'utf8') readFileThunk((err, data) => { // ... })</code></pre><p>单纯的 thunk 函数并没有很大的用处, 大牛们想到了和 generator 结合:</p><pre><code class="language-js">function* readFileThunkWithGen() { try { const content1 = yield readFileThunk('/etc/passwd', 'utf8') console.log(content1) const content2 = yield readFileThunk('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { console.error(err) return 'fail' } } const g = readFileThunkWithGen() g.next().value((err, data) => { if (err) { return g.throw(err).value } g.next(data.toString()).value((err, data) => { if (err) { return g.throw(err).value } g.next(data.toString()) }) })</code></pre><p>thunk 函数的真正作用是统一多参数函数的调用方式,在 next 调用时把控制权交还给 generator,使 generator 函数可以使用递归方式自启动流程</p><pre><code class="language-js">const run = generator => { const g = generator() const next = (err, ...rest) => { if (err) { return g.throw(err).value } const result = g.next(rest.length > 1 ? rest : rest[0]) if (result.done) { return result.value } result.value(next) } next() } run(readFileThunkWithGen)</code></pre><p>有了自启动的加持之后,generator 函数内就可以写"同步"的代码了。generator 函数也可以与 Promise 结合:</p><pre><code class="language-js">function* readFileWithGen() { try { const content1 = yield readFileWithPromise('/etc/passwd', 'utf8') console.log(content1) const content2 = yield readFileWithPromise('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { console.error(err) return 'fail' } } const run = generator => { return new Promise((resolve, reject) => { const g = generator() const next = res => { const result = g.next(res) if (result.done) { return resolve(result.value) } result.value .then( next, err => reject(gen.throw(err).value) ) } next() }) } run(readFileWithGen) .then(res => console.log(res)) .catch(err => console.log(err))</code></pre><p>generator 可以暂停执行,很容易让它和异步操作产生联系,因为我们在处理异步操作时,在等待的时候可以暂停当前任务,把程序控制权交还给其他程序,当异步任务有返回时,在回调中再把控制权交还给之前的任务。generator 实际上并没有改变 JavaScript 单线程、使用回调处理异步任务的本质。</p><h2>3. co 函数库</h2><p>每次执行 generator 函数时自己写启动器比较麻烦。 <a href="https://github.com/tj/co" target="">co函数库</a> 是一个 generator 函数的自启动执行器,使用条件是 generator 函数的 yield 命令后面,只能是 thunk 函数或 Promise 对象,co 函数执行完返回一个 Promise 对象。</p><pre><code class="language-js">const co = require('co') co(readFileWithGen).then(res => console.log(res)) // 'done' co(readFileThunkWithGen).then(res => console.log(res)) // 'done'</code></pre><p>co 函数库的源码实现其实就是把上面两种情况做了综合:</p><pre><code class="language-js">// 做了简化,与源码基本一致 const co = (generator, ...rest) => { const ctx = this return new Promise((resolve, reject) => { const gen = generator.call(ctx, ...rest) if (!gen || typeof gen.next !== 'function') { return resolve(gen) } const onFulfilled = res => { let ret try { ret = gen.next(res) } catch (e) { return reject(e) } next(ret) } const onRejected = err => { let ret try { ret = gen.throw(err) } catch (e) { return reject(e) } next(ret) } const next = result => { if (result.done) { return resolve(result.value) } toPromise(result.value).then(onFulfilled, onRejected) } onFulfilled() }) } const toPromise = value => { if (isPromise(value)) return value if ('function' == typeof value) { return new Promise((resolve, reject) => { value((err, ...rest) => { if (err) { return reject(err) } resolve(rest.length > 1 ? rest : rest[0]) }) }) } } </code></pre><h2>4. 理解 async、await</h2><p>一句话,async、await 是 co 库的官方实现。也可以看作自带启动器的 generator 函数的语法糖。不同的是,async、await 只支持 Promise 和原始类型的值,不支持 thunk 函数。</p><pre><code class="language-js">// generator with co co(function* () { try { const content1 = yield readFileWithPromise('/etc/passwd', 'utf8') console.log(content1) const content2 = yield readFileWithPromise('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { console.error(err) return 'fail' } }) // async await async function readfile() { try { const content1 = await readFileWithPromise('/etc/passwd', 'utf8') console.log(content1) const content2 = await readFileWithPromise('/etc/profile', 'utf8') console.log(content2) return 'done' } catch (err) { throw(err) } } readfile().then( res => console.log(res), err => console.error(err) )</code></pre><h1>总结</h1><p>不论以上哪种方式,都没有改变 JavaScript 单线程、使用回调处理异步任务的本质。人类总是追求最简单易于理解的编程方式。</p>
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
标签
Html/JS/CSS
未设置
标签管理
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/DreamCoders/CoderGuide.git
git@gitee.com:DreamCoders/CoderGuide.git
DreamCoders
CoderGuide
CoderGuide
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册