热门标签 | 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 函数

推荐阅读
  • 本文介绍了如何在 Node.js 中使用 `setDefaultEncoding` 方法为可写流设置默认编码,并提供了详细的语法说明和示例代码。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • 本文介绍如何使用 Angular 6 的 HttpClient 模块来获取 HTTP 响应头,包括代码示例和常见问题的解决方案。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 云函数与数据库API实现增删查改的对比
    本文将深入探讨使用云函数和数据库API实现数据操作(增删查改)的不同方法,通过详细的代码示例帮助读者更好地理解和掌握这些技术。文章不仅提供代码实现,还解释了每种方法的特点和适用场景。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 本文介绍了如何在PHP Magento模型中自定义主键,避免使用默认的自动递增主键,并提供了解决方案和代码示例。 ... [详细]
  • 本文探讨了如何利用HTML5和JavaScript在浏览器中进行本地文件的读取和写入操作,并介绍了获取本地文件路径的方法。HTML5提供了一系列API,使得这些操作变得更加简便和安全。 ... [详细]
  • Logback使用小结
    1一定要使用slf4j的jar包,不要使用apachecommons的jar。否则滚动生成文件不生效,不滚动的时候却生效~~importorg.slf ... [详细]
  • 本文提供了多种方法来计算给定年份和月份的起始日和结束日,并进一步探讨了如何根据年、月、周获取特定周的起始日和结束日。 ... [详细]
  • [Vue.js 3.0] Guide – Scaling Up – State Management
    [Vue.js 3.0] Guide – Scaling Up – State Management ... [详细]
  • 本文探讨了在React项目中实现子组件向父组件传递数据的方法,包括通过回调函数和使用React状态管理工具。 ... [详细]
  • 本文介绍了如何在多线程环境中实现异步任务的事务控制,确保任务执行的一致性和可靠性。通过使用计数器和异常标记字段,系统能够准确判断所有异步线程的执行结果,并根据结果决定是否回滚或提交事务。 ... [详细]
  • 微信小程序中实现位置获取的全面指南
    本文详细介绍了如何在微信小程序中实现地理位置的获取,包括通过微信官方API和腾讯地图API两种方式。文中不仅涵盖了必要的准备工作,如申请开发者密钥、下载并配置SDK等,还提供了处理用户授权及位置信息获取的具体代码示例。 ... [详细]
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社区 版权所有