资讯专栏INFORMATION COLUMN

从Google V8引擎剖析Promise实现

plokmju88 / 558人阅读

摘要:从引擎剖析实现本文阅读的源码为,此版本的实现为版本,在后续版本继续对其实现进行了处理。引入了语法等,在版本迭代后,逐渐迭代成了版本实现。方法的调用暴露给外部的高版本为方法,如果当前的指向的构造函数,则设置当前的状态,否则调用函数执行。

从Google V8引擎剖析Promise实现

</>复制代码

  1. ​ 本文阅读的源码为Google V8 Engine v3.29.45,此版本的promise实现为js版本,在后续版本Google继续对其实现进行了处理。引入了es6语法等,在7.X版本迭代后,逐渐迭代成了C版本实现。

    ​ 贴上源码地址:https://chromium.googlesource... 大家自觉传送。

  2. ​ 代码中所有类似%functionName的函数均是C语言实现的运行时函数。

Define variables

首先定义了将要在JS作用域使用了一些变量,提高了编译器的效率。

</>复制代码

  1. var IsPromise;
  2. var PromiseCreate;
  3. var PromiseResolve;
  4. var PromiseReject;
  5. var PromiseChain;
  6. var PromiseCatch;
  7. var PromiseThen;
  8. var PromiseHasRejectHandler;

随后定义了一些全局私有变量供给和C语音交互,用于维护Promise的状态和进行Debug。

</>复制代码

  1. var promiseStatus = GLOBAL_PRIVATE("Promise#status");
  2. var promiseValue = GLOBAL_PRIVATE("Promise#value");
  3. var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
  4. var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
  5. var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
  6. var promiseDebug = GLOBAL_PRIVATE("Promise#debug");
  7. var lastMicrotaskId = 0;

其中GLOBAL_PRIVATE是python进行实现的,运用python的宏定义(macro)来定义调用了C语言的CreateGlobalPrivateOwnSymbol方法。

</>复制代码

  1. macro GLOBAL_PRIVATE(name) = (%CreateGlobalPrivateOwnSymbol(name));

随后运用了一个自执行的匿名函数进行闭包逻辑。

</>复制代码

  1. (function() {
  2. // 主逻辑
  3. })();

在闭包逻辑的最后,在promise原型上挂载了三个方法:chain,then,catch。在promise对象上挂载了all,race等六个方法。将Promise对象注册到了global。

</>复制代码

  1. %AddNamedProperty(global, "Promise", $Promise, DONT_ENUM);
  2. InstallFunctions($Promise, DONT_ENUM, [
  3. "defer", PromiseDeferred,
  4. "accept", PromiseResolved,
  5. "reject", PromiseRejected,
  6. "all", PromiseAll,
  7. "race", PromiseOne,
  8. "resolve", PromiseCast
  9. ]);
  10. InstallFunctions($Promise.prototype, DONT_ENUM, [
  11. "chain", PromiseChain,
  12. "then", PromiseThen,
  13. "catch", PromiseCatch
  14. ]);
Start from constructor

</>复制代码

  1. var $Promise = function Promise(resolver) {
  2. // 如果传入参数为全局promiseRaw变量的时候return
  3. if (resolver === promiseRaw) return;
  4. // 如果当前函数不是构造函数的化,抛出错误这不是一个promise
  5. if (!%_IsConstructCall()) throw MakeTypeError("not_a_promise", [this]);
  6. // 如果传入参数不是一个函数的话,抛出错误,传入参数不是一个function
  7. if (!IS_SPEC_FUNCTION(resolver))
  8. throw MakeTypeError("resolver_not_a_function", [resolver]);
  9. var promise = PromiseInit(this);
  10. try {
  11. // debug相关忽略
  12. %DebugPushPromise(promise);
  13. resolver(function(x) { PromiseResolve(promise, x) },
  14. function(r) { PromiseReject(promise, r) });
  15. } catch (e) {
  16. // 报错之后走到错误处理函数
  17. PromiseReject(promise, e);
  18. } finally {
  19. // debug相关忽略
  20. %DebugPopPromise();
  21. }
  22. }

构造函数在做完额外的异常和参数判断后,进入主逻辑调用PromiseInit方法初始化promise,随后调用了resolver方法,传入了两个默认的处理函数。在promise在内部被调用时(PromiseDeferred方法被调用时)会实例化$promise,将默认方法return回去,使得创建的promise示例具有resolve和reject方法。

</>复制代码

  1. function PromiseDeferred() {
  2. if (this === $Promise) {
  3. // Optimized case, avoid extra closure.
  4. var promise = PromiseInit(new $Promise(promiseRaw));
  5. return {
  6. promise: promise,
  7. resolve: function(x) { PromiseResolve(promise, x) },
  8. reject: function(r) { PromiseReject(promise, r) }
  9. };
  10. } else {
  11. var result = {};
  12. result.promise = new this(function(resolve, reject) {
  13. result.resolve = resolve;
  14. result.reject = reject;
  15. })
  16. return result;
  17. }
  18. }
PromiseInit

</>复制代码

  1. function PromiseSet(promise, status, value, onResolve, onReject) {
  2. // macro SET_PRIVATE(obj, sym, val) = (obj[sym] = val);
  3. // 设置promise的状态,SET_PRIVATE只有在给已经存在的对象设置已有属性值的时候才会被调用
  4. SET_PRIVATE(promise, promiseStatus, status);
  5. SET_PRIVATE(promise, promiseValue, value);
  6. SET_PRIVATE(promise, promiseOnResolve, onResolve);
  7. SET_PRIVATE(promise, promiseOnReject, onReject);
  8. // debug代码忽略
  9. if (DEBUG_IS_ACTIVE) {
  10. %DebugPromiseEvent({ promise: promise, status: status, value: value });
  11. }
  12. return promise;
  13. }
  14. function PromiseInit(promise) {
  15. return PromiseSet(
  16. promise, 0, UNDEFINED, new InternalArray, new InternalArray)
  17. }

实质上是调用了PromiseSet方法给promise设置了当前的状态。

PromiseResolve

promiseResolve方法的调用暴露给外部的promise.accept(高版本为resolve)方法,如果当前的this指向promise的构造函数,则设置当前的promise状态,否则调用resolve函数执行。

</>复制代码

  1. function PromiseResolved(x) {
  2. if (this === $Promise) {
  3. // Optimized case, avoid extra closure.
  4. return PromiseSet(new $Promise(promiseRaw), +1, x);
  5. } else {
  6. return new this(function(resolve, reject) { resolve(x) });
  7. }
  8. }

promiseResolve处理逻辑同promiseReject,不再赘述。

promise.then

PromiseThen方法的调用暴露给实例化后的promise.then方法调用。

</>复制代码

  1. PromiseThen = function PromiseThen(onResolve, onReject) {
  2. onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
  3. : PromiseIdResolveHandler;
  4. onReject = IS_SPEC_FUNCTION(onReject) ? onReject
  5. : PromiseIdRejectHandler;
  6. var that = this;
  7. var constructor = this.constructor;
  8. return %_CallFunction(
  9. this,
  10. function(x) {
  11. x = PromiseCoerce(constructor, x);
  12. return x === that ? onReject(MakeTypeError("promise_cyclic", [x])) :
  13. IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
  14. },
  15. onReject,
  16. PromiseChain
  17. );
  18. }

首先判断传入的两个参数是否是函数,不是的话添加默认的处理函数,做良好的容错处理。而后调用了 %_CallFunction方法(第一个参数是this,最后一个参数是要调用的方法,中间是传入参数),类似Function.prototype.call()方法,调用了PromiseChain方法,传入了两个参数resolve和reject。在resolve方法内部调用了PromiseCoerce方法,生成对象如果是个thenable对象调用对象的then方法否则直接onResolve方法。

PromiseCoerce

</>复制代码

  1. function PromiseCoerce(constructor, x) {
  2. if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
  3. var then;
  4. try {
  5. then = x.then;
  6. } catch(r) {
  7. return %_CallFunction(constructor, r, PromiseRejected);
  8. }
  9. // macro IS_SPEC_FUNCTION(arg) = (%_ClassOf(arg) === "Function");
  10. // 如果是一个function
  11. if (IS_SPEC_FUNCTION(then)) {
  12. var deferred = %_CallFunction(constructor, PromiseDeferred);
  13. try {
  14. %_CallFunction(x, deferred.resolve, deferred.reject, then);
  15. } catch(r) {
  16. deferred.reject(r);
  17. }
  18. return deferred.promise;
  19. }
  20. }
  21. return x;
  22. }

核心的逻辑是如果传入对象的then属性是一个function,则调用then方法。若有报错走到reject方法。

PromiseChain

</>复制代码

  1. PromiseChain = function PromiseChain(onResolve, onReject) {
  2. // 补默认的处理函数
  3. onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
  4. onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
  5. var deferred = %_CallFunction(this.constructor, PromiseDeferred);
  6. switch (GET_PRIVATE(this, promiseStatus)) {
  7. case UNDEFINED:
  8. throw MakeTypeError("not_a_promise", [this]);
  9. case 0: // Pending
  10. GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
  11. GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
  12. break;
  13. case +1: // Resolved
  14. PromiseEnqueue(GET_PRIVATE(this, promiseValue),
  15. [onResolve, deferred],
  16. +1);
  17. break;
  18. case -1: // Rejected
  19. PromiseEnqueue(GET_PRIVATE(this, promiseValue),
  20. [onReject, deferred],
  21. -1);
  22. break;
  23. }
  24. // debug代码忽略
  25. if (DEBUG_IS_ACTIVE) {
  26. %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
  27. }
  28. return deferred.promise;
  29. }

PromiseChain方法是promise实现的核心,判断当前定义的promise状态,如果是pending状态在promiseOnResolve数组中push当前的onResolve方法。如果是Resolved状态或者Rejected状态,则调用PromiseEnqueue函数进行微任务的添加。

PromiseEnqueue

</>复制代码

  1. function PromiseEnqueue(value, tasks, status) {
  2. var id, name, instrumenting = DEBUG_IS_ACTIVE;
  3. %EnqueueMicrotask(function() {
  4. if (instrumenting) {
  5. %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
  6. }
  7. for (var i = 0; i < tasks.length; i += 2) {
  8. PromiseHandle(value, tasks[i], tasks[i + 1])
  9. }
  10. if (instrumenting) {
  11. %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
  12. }
  13. });
  14. if (instrumenting) {
  15. id = ++lastMicrotaskId;
  16. name = status > 0 ? "Promise.resolve" : "Promise.reject";
  17. %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
  18. }
  19. }

​ 此步骤其实是将PromiseHandle函数加入JS运行时的微任务队列中。微任务的队列列表是C语言进行维护的,应用%EnqueueMicrotask方法进行添加。

PromiseHandle

</>复制代码

  1. function PromiseHandle(value, handler, deferred) {
  2. try {
  3. %DebugPushPromise(deferred.promise);
  4. var result = handler(value);
  5. if (result === deferred.promise)
  6. throw MakeTypeError("promise_cyclic", [result]);
  7. else if (IsPromise(result))
  8. %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
  9. else
  10. deferred.resolve(result);
  11. } catch (exception) {
  12. try { deferred.reject(exception); } catch (e) { }
  13. } finally {
  14. %DebugPopPromise();
  15. }
  16. }

此函数处理了传入的方法,是指是调用了resolve方法,如果返回的结果依旧是一个promise则继续调用PromiseChain方法,否则调用新生成的promise实例的resolve方法,进而实现循坏调用。

promise.all

promise的all方法实现了发送多个promise请求,返回一个新的promise,所有promise打到resolve状态时触发resolve状态,若有一个promise被reject,则返回此promise的reject原因。

</>复制代码

  1. function PromiseAll(values) {
  2. var deferred = %_CallFunction(this, PromiseDeferred);
  3. var resolutions = [];
  4. if (!%_IsArray(values)) {
  5. deferred.reject(MakeTypeError("invalid_argument"));
  6. return deferred.promise;
  7. }
  8. try {
  9. var count = values.length;
  10. if (count === 0) {
  11. deferred.resolve(resolutions);
  12. } else {
  13. for (var i = 0; i < values.length; ++i) {
  14. this.resolve(values[i]).then(
  15. (function() {
  16. // Nested scope to get closure over current i (and avoid .bind).
  17. // TODO(rossberg): Use for-let instead once available.
  18. var i_captured = i;
  19. return function(x) {
  20. resolutions[i_captured] = x;
  21. if (--count === 0) deferred.resolve(resolutions);
  22. };
  23. })(),
  24. function(r) { deferred.reject(r) }
  25. );
  26. }
  27. }
  28. } catch (e) {
  29. deferred.reject(e)
  30. }
  31. return deferred.promise;
  32. }

首先判断传参的合理性,生成一个新的promise对象,利用遍历的方式给每个传入的promise的resolve方法后都追加了then方法,使得每个传入的promise执行then方法后凑执行判断逻辑,当计数count的flag为0的时候,所有promise resolve完成,调用新promise对象的resolve方法,传入新promise的reject方法作为then方法reject参数。使得所有promise的reject函数被调用时都会走到新promise对象的reject,最后返回新生成的promise。

Summary

Promise的状态和核心变量均托管到公共的作用域去维护,通过数组的push方法去添加Promise自定的resolve和reject方法。并将resolve和reject方法的执行加入微服务队列中,等到resolve方法被调用时执行resolve(value)方法进行调用。为了实现promise的循环嵌套调用,在每次处理value之前将处理逻辑之上包裹了一层新的promise逻辑,类似(new promise()).then(resolve(value)),思路如下。

</>复制代码

  1. var ref = function (value) {
  2. if (value && value.then)
  3. return value;
  4. return {
  5. then: function (callback) {
  6. // 实例化一个新的promise
  7. var result = defer();
  8. // 进入宏任务队列
  9. enqueue(function () {
  10. result.resolve(callback(value));
  11. });
  12. return result.promise;
  13. }
  14. };
  15. };
Reference

promise设计思想:https://github.com/kriskowal/...

JavaScript执行机制:https://www.jianshu.com/p/17c...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109724.html

相关文章

  • JS引擎理解Await b()与Promise.then(b)的堆栈处理

    摘要:对于引擎来说,两者获取堆栈的方式是不同的。对于引擎来说,堆栈信息附加在了函数所返回的并在链中传递,这样函数也能在需要的时候获取堆栈信息。使用可以实时监控线上应用的错误,并获取完整的堆栈信息。 译者按: Async/Await真的只是简单的语法糖吗?No! 原文:Asynchronous stack traces: why await beats .then() 作者: Math...

    ziwenxie 评论0 收藏0
  • Promisify 的源码解析

    摘要:参考文档升级后的函数回调参数问题中的使用方法和还是不一样的源码讲解的内部机制优化相关内容文章官方文档简述使用过的都知道这个方法的作用,通过该方法会让形式的函数风格转换成方法,可以认为是一颗语法糖,例如接下来我们就分析一下这个的内部流程。 参考文档 升级bluebird 3后Promise.promisify的函数回调参数问题:3中的使用方法和2还是不一样的 How does Bl...

    gougoujiang 评论0 收藏0
  • 2017文章总结

    摘要:欢迎来我的个人站点性能优化其他优化浏览器关键渲染路径开启性能优化之旅高性能滚动及页面渲染优化理论写法对压缩率的影响唯快不破应用的个优化步骤进阶鹅厂大神用直出实现网页瞬开缓存网页性能管理详解写给后端程序员的缓存原理介绍年底补课缓存机制优化动 欢迎来我的个人站点 性能优化 其他 优化浏览器关键渲染路径 - 开启性能优化之旅 高性能滚动 scroll 及页面渲染优化 理论 | HTML写法...

    dailybird 评论0 收藏0
  • 2017文章总结

    摘要:欢迎来我的个人站点性能优化其他优化浏览器关键渲染路径开启性能优化之旅高性能滚动及页面渲染优化理论写法对压缩率的影响唯快不破应用的个优化步骤进阶鹅厂大神用直出实现网页瞬开缓存网页性能管理详解写给后端程序员的缓存原理介绍年底补课缓存机制优化动 欢迎来我的个人站点 性能优化 其他 优化浏览器关键渲染路径 - 开启性能优化之旅 高性能滚动 scroll 及页面渲染优化 理论 | HTML写法...

    hellowoody 评论0 收藏0

发表评论

0条评论

plokmju88

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<