热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

图解GoogleV8#19:异步编程(二):V8是如何实现async/await的?

说明图解GoogleV8学习笔记前端异步编程的方案史1、什么是回调地狱?如果在代码中过多地使用异步回调函数,会将整个代码逻辑打乱,从

说明

图解 Google V8 学习笔记

前端异步编程的方案史

在这里插入图片描述

1、什么是回调地狱?

如果在代码中过多地使用异步回调函数,会将整个代码逻辑打乱,从而让代码变得难以理解,这就是回调地狱问题。

var fs = require('fs')fs.readFile('./src/kaimo555.txt', 'utf-8', function(err, data) {if (err) {throw err}console.log(data)fs.readFile('./src/kaimo666.txt', 'utf-8', function(err, data) {if (err) {throw err}console.log(data)fs.readFile('./src/kaimo777.txt', 'utf-8', function(err, data) {if (err) {throw err}console.log(data)})})
})

上面的代码一个异步请求套着一个异步请求,一个异步请求依赖于另一个的执行结果,使用回调的方式相互嵌套。
这会导致代码很丑陋,不方便后期维护。

2、使用 Promise 解决回调地狱问题

使用 Promise 可以解决回调地狱中编码不线性的问题。

const fs = require("fs")const p = new Promise((resolve, reject) => {fs.readFile("./src/kaimo555.txt", (err, data) => {resolve(data)})
})
p.then(value => {return new Promise((resolve, reject) => {fs.readFile("./src/kaimo666.txt", (err, data) => {resolve([value, data])})})
}).then(value => {return new Promise((resolve, reject) => {fs.readFile("./src/kaimo777.txt", (err, data) => {value.push(data)resolve(value)})})
}).then(value => {let str = value.join("\n")console.log(str)
})

3、使用 Generator 函数实现更加线性化逻辑

虽然使用 Promise 可以解决回调地狱中编码不线性的问题,但这种方式充满了 Promise 的 then() 方法,如果处理流程比较复杂的话,那么整段代码将充斥着大量的 then,异步逻辑之间依然被 then 方法打断了,因此这种方式的语义化不明显,代码不能很好地表示执行流程。

那么怎么才能像编写同步代码的方式来编写异步代码?

例子:

function getResult(){let id = getUserID(); // 异步请求let name = getUserName(id); // 异步请求return name}

可行的方案就是执行到异步请求的时候,暂停当前函数,等异步请求返回了结果,再恢复该函数。

大致模型图:关键就是实现函数暂停执行和函数恢复执行

在这里插入图片描述

生成器函数

生成器就是为了实现暂停函数和恢复函数而设计的,生成器函数是一个带星号函数,配合 yield 就可以实现函数的暂停和恢复。恢复生成器的执行,可以使用 next 方法。

例子:

function* getResult() {yield 'getUserID'yield 'getUserName'return 'name'
}let result = getResult()console.log(result.next().value)
console.log(result.next().value)
console.log(result.next().value)

V8 是怎么实现生成器函数的暂停执行和恢复执行?


协程

协程是一种比线程更加轻量级的存在。 如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程。

  • 一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。
  • 协程不是被操作系统内核所管理,而完全是由程序所控制,不会像线程切换那样消耗资源。

上面例子的协程执行流程图大致如下:

在这里插入图片描述

协程和 Promise 相互配合执行的大致流程:

function* getResult() {let id_res = yield fetch(id_url);let id_text = yield id_res.text();let new_name_url = name_url + "?id=" + id_text;let name_res = yield fetch(new_name_url);let name_text = yield name_res.text();
}let result = getResult()
result.next().value.then((response) => {return result.next(response).value
}).then((response) => {return result.next(response).value
}).then((response) => {return result.next(response).value
}).then((response) => {return result.next(response).value

执行器

把执行生成器的代码封装成一个函数,这个函数驱动了生成器函数继续往下执行,我们把这个执行生成器代码的函数称为执行器

可以参考著名的 co 框架

在这里插入图片描述

function* getResult() {let id_res = yield fetch(id_url);let id_text = yield id_res.text();let new_name_url = name_url + "?id=" + id_text;let name_res = yield fetch(new_name_url);let name_text = yield name_res.text();
}co(getResult())

co 源码实现原理:其实就是通过不断的调用 generator 函数的 next() 函数,来达到自动执行 generator 函数的效果(类似 async、await 函数的自动自行)。

/*** slice() reference.*/var slice = Array.prototype.slice;/*** Expose `co`.*/module.exports = co['default'] = co.co = co;/*** Wrap the given generator `fn` into a* function that returns a promise.* This is a separate function so that* every `co()` call doesn't create a new,* unnecessary closure.** @param {GeneratorFunction} fn* @return {Function}* @api public*/co.wrap = function (fn) {createPromise.__generatorFunction__ = fn;return createPromise;function createPromise() {return co.call(this, fn.apply(this, arguments));}
};/*** Execute the generator function or a generator* and return a promise.** @param {Function} fn* @return {Promise}* @api public*/function co(gen) {var ctx = this;var args = slice.call(arguments, 1);// we wrap everything in a promise to avoid promise chaining,// which leads to memory leak errors.// see https://github.com/tj/co/issues/180return new Promise(function(resolve, reject) {if (typeof gen === 'function') gen = gen.apply(ctx, args);if (!gen || typeof gen.next !== 'function') return resolve(gen);onFulfilled();/*** @param {Mixed} res* @return {Promise}* @api private*/function onFulfilled(res) {var ret;try {ret = gen.next(res);} catch (e) {return reject(e);}next(ret);return null;}/*** @param {Error} err* @return {Promise}* @api private*/function onRejected(err) {var ret;try {ret = gen.throw(err);} catch (e) {return reject(e);}next(ret);}/*** Get the next value in the generator,* return a promise.** @param {Object} ret* @return {Promise}* @api private*/function next(ret) {if (ret.done) return resolve(ret.value);var value = toPromise.call(ctx, ret.value);if (value && isPromise(value)) return value.then(onFulfilled, onRejected);return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '+ 'but the following object was passed: "' + String(ret.value) + '"'));}});
}/*** Convert a `yield`ed value into a promise.** @param {Mixed} obj* @return {Promise}* @api private*/function toPromise(obj) {if (!obj) return obj;if (isPromise(obj)) return obj;if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);if ('function' == typeof obj) return thunkToPromise.call(this, obj);if (Array.isArray(obj)) return arrayToPromise.call(this, obj);if (isObject(obj)) return objectToPromise.call(this, obj);return obj;
}/*** Convert a thunk to a promise.** @param {Function}* @return {Promise}* @api private*/function thunkToPromise(fn) {var ctx = this;return new Promise(function (resolve, reject) {fn.call(ctx, function (err, res) {if (err) return reject(err);if (arguments.length > 2) res = slice.call(arguments, 1);resolve(res);});});
}/*** Convert an array of "yieldables" to a promise.* Uses `Promise.all()` internally.** @param {Array} obj* @return {Promise}* @api private*/function arrayToPromise(obj) {return Promise.all(obj.map(toPromise, this));
}/*** Convert an object of "yieldables" to a promise.* Uses &#96;Promise.all()&#96; internally.** &#64;param {Object} obj* &#64;return {Promise}* &#64;api private*/function objectToPromise(obj){var results &#61; new obj.constructor();var keys &#61; Object.keys(obj);var promises &#61; [];for (var i &#61; 0; i < keys.length; i&#43;&#43;) {var key &#61; keys[i];var promise &#61; toPromise.call(this, obj[key]);if (promise && isPromise(promise)) defer(promise, key);else results[key] &#61; obj[key];}return Promise.all(promises).then(function () {return results;});function defer(promise, key) {// predefine the key in the resultresults[key] &#61; undefined;promises.push(promise.then(function (res) {results[key] &#61; res;}));}
}/*** Check if &#96;obj&#96; is a promise.** &#64;param {Object} obj* &#64;return {Boolean}* &#64;api private*/function isPromise(obj) {return &#39;function&#39; &#61;&#61; typeof obj.then;
}/*** Check if &#96;obj&#96; is a generator.** &#64;param {Mixed} obj* &#64;return {Boolean}* &#64;api private*/function isGenerator(obj) {return &#39;function&#39; &#61;&#61; typeof obj.next && &#39;function&#39; &#61;&#61; typeof obj.throw;
}/*** Check if &#96;obj&#96; is a generator function.** &#64;param {Mixed} obj* &#64;return {Boolean}* &#64;api private*/function isGeneratorFunction(obj) {var constructor &#61; obj.constructor;if (!constructor) return false;if (&#39;GeneratorFunction&#39; &#61;&#61;&#61; constructor.name || &#39;GeneratorFunction&#39; &#61;&#61;&#61; constructor.displayName) return true;return isGenerator(constructor.prototype);
}/*** Check for plain object.** &#64;param {Mixed} val* &#64;return {Boolean}* &#64;api private*/function isObject(val) {return Object &#61;&#61; val.constructor;
}

4、async/await&#xff1a;异步编程的“终极”方案

生成器依然需要使用额外的 co 函数来驱动生成器函数的执行&#xff0c;基于这个原因&#xff0c;ES7 引入了 async/await&#xff0c;这是 Javascript 异步编程的一个重大改进&#xff0c;它改进了生成器的缺点&#xff0c;提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力

  • async/await 不是 generator promise 的语法糖&#xff0c;而是从设计到开发都是一套完整的体系&#xff0c;只不过使用了协程和 promise
  • async/await 支持 try catch 也是引擎的底层实现的

async function getResult() {try {let id_res &#61; await fetch(id_url);let id_text &#61; await id_res.text();let new_name_url &#61; name_url&#43;"?id&#61;"&#43;id_text;let name_res &#61; await fetch(new_name_url);let name_text &#61; await name_res.text();} catch (err) {console.error(err)}
}
getResult()

async

async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。

MDN&#xff1a;async 函数

在这里插入图片描述

V8 是如何处理 await 后面的内容&#xff1f;

await 可以等待两种类型的表达式&#xff1a;

  • 任何普通表达式
  • 一个 Promise 对象的表达式

如果 await 等待的是一个 Promise 对象&#xff0c;它就会暂停执行生成器函数&#xff0c;直到 Promise 对象的状态变成 resolve&#xff0c;才会恢复执行&#xff0c;然后得到 resolve 的值&#xff0c;作为 await 表达式的运算结果。

拓展资料


  • 《学习 koa 源码的整体架构&#xff0c;浅析koa洋葱模型原理和co原理》
  • MDN&#xff1a;async 函数

推荐阅读
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 本文介绍了在序列化时如何对SnakeYaml应用格式化,包括通过设置类和DumpSettings来实现定制输出的方法。作者提供了一个示例,展示了期望的yaml生成格式,并解释了如何使用SnakeYaml的特定设置器来实现这个目标。对于正在使用SnakeYaml进行序列化的开发者来说,本文提供了一些有用的参考和指导。摘要长度为169字。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 本文介绍了如何使用elementui分页组件进行分页功能的改写,只需一行代码即可调用。通过封装分页组件,避免在每个页面都写跳转请求的重复代码。详细的代码示例和使用方法在正文中给出。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
  • 本文讨论了在使用PHP cURL发送POST请求时,请求体在node.js中没有定义的问题。作者尝试了多种解决方案,但仍然无法解决该问题。同时提供了当前PHP代码示例。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
author-avatar
-_-小欢欢-_-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有