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

推荐阅读
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社区 版权所有