从Google V8引擎剖析Promise实现

更新日期: 2019-05-24阅读: 3k标签: 引擎

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

贴上源码地址:https://chromium.googlesource... 大家自觉传送。代码中所有类似%functionName的函数均是C语言实现的运行时函数。


Define variables

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

var IsPromise;
var PromiseCreate;
var PromiseResolve;
var PromiseReject;
var PromiseChain;
var PromiseCatch;
var PromiseThen;
var PromiseHasRejectHandler;

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

var promiseStatus = GLOBAL_PRIVATE("Promise#status");
var promiseValue = GLOBAL_PRIVATE("Promise#value");
var promiseonResolve = GLOBAL_PRIVATE("Promise#onResolve");
var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
var promiseDebug = GLOBAL_PRIVATE("Promise#debug");
var lastMicrotaskId = 0;

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

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

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

(function() {
  // 主逻辑
})();

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

%AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM);
InstallFunctions($Promise, DONT_ENUM, [
    "defer", PromiseDeferred,
    "accept", PromiseResolved,
    "reject", PromiseRejected,
    "all", PromiseAll,
    "race", PromiseOne,
    "resolve", PromiseCast
]);
InstallFunctions($Promise.prototype, DONT_ENUM, [
    "chain", PromiseChain,
    "then", PromiseThen,
    "catch", PromiseCatch
]);

Start from constructor

var $Promise = function Promise(resolver) {
    // 如果传入参数为全局promiseRaw变量的时候return
    if (resolver === promiseRaw) return;
    // 如果当前函数不是构造函数的化,抛出错误这不是一个promise
    if (!%_IsConstructCall()) throw MakeTypeError('not_a_promise', [this]);
    // 如果传入参数不是一个函数的话,抛出错误,传入参数不是一个function
    if (!IS_SPEC_FUNCTION(resolver))
        throw MakeTypeError('resolver_not_a_function', [resolver]);
    var promise = PromiseInit(this);
    try {
        // debug相关忽略
        %DebugPushPromise(promise);
        resolver(function(x) { PromiseResolve(promise, x) },
                 function(r) { PromiseReject(promise, r) });
    } catch (e) {
        // 报错之后走到错误处理函数
        PromiseReject(promise, e);
    } finally {
        // debug相关忽略
        %DebugPopPromise();
    }
}

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

function PromiseDeferred() {
    if (this === $Promise) {
        // Optimized case, avoid extra closure.
        var promise = PromiseInit(new $Promise(promiseRaw));
        return {
            promise: promise,
            resolve: function(x) { PromiseResolve(promise, x) },
            reject: function(r) { PromiseReject(promise, r) }
        };
    } else {
        var result = {};
        result.promise = new this(function(resolve, reject) {
            result.resolve = resolve;
            result.reject = reject;
        })
        return result;
    }
}

PromiseInit

function PromiseSet(promise, status, value, onResolve, onReject) {
    // macro SET_PRIVATE(obj, sym, val) = (obj[sym] = val);
    // 设置promise的状态,SET_PRIVATE只有在给已经存在的对象设置已有属性值的时候才会被调用
    SET_PRIVATE(promise, promiseStatus, status);
    SET_PRIVATE(promise, promiseValue, value);
    SET_PRIVATE(promise, promiseOnResolve, onResolve);
    SET_PRIVATE(promise, promiseOnReject, onReject);
    // debug代码忽略
    if (DEBUG_IS_ACTIVE) {
        %DebugPromiseEvent({ promise: promise, status: status, value: value });
    }
    return promise;
}

function PromiseInit(promise) {
    return PromiseSet(
        promise, 0, UNDEFINED, new InternalArray, new InternalArray)
}

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

PromiseResolve

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

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

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


promise.then

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

PromiseThen = function PromiseThen(onResolve, onReject) {
    onResolve = IS_SPEC_FUNCTION(onResolve) ? onResolve
    : PromiseIdResolveHandler;
    onReject = IS_SPEC_FUNCTION(onReject) ? onReject
    : PromiseIdRejectHandler;
    var that = this;
    var constructor = this.constructor;
    return %_CallFunction(
        this,
        function(x) {
            x = PromiseCoerce(constructor, x);
            return x === that ? onReject(MakeTypeError('promise_cyclic', [x])) :
            IsPromise(x) ? x.then(onResolve, onReject) : onResolve(x);
        },
        onReject,
        PromiseChain
    );
}

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


PromiseCoerce

function PromiseCoerce(constructor, x) {
    if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
        var then;
        try {
            then = x.then;
        } catch(r) {
            return %_CallFunction(constructor, r, PromiseRejected);
        }
        // macro IS_SPEC_FUNCTION(arg) = (%_ClassOf(arg) === 'Function');
        // 如果是一个function
        if (IS_SPEC_FUNCTION(then)) {
            var deferred = %_CallFunction(constructor, PromiseDeferred);
            try {
                %_CallFunction(x, deferred.resolve, deferred.reject, then);
            } catch(r) {
                deferred.reject(r);
            }
            return deferred.promise;
        }
    }
    return x;
}

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


PromiseChain

PromiseChain = function PromiseChain(onResolve, onReject) {  
    // 补默认的处理函数
    onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
    onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
    var deferred = %_CallFunction(this.constructor, PromiseDeferred);
    switch (GET_PRIVATE(this, promiseStatus)) {
        case UNDEFINED:
            throw MakeTypeError('not_a_promise', [this]);
        case 0:  // Pending
            GET_PRIVATE(this, promiseOnResolve).push(onResolve, deferred);
            GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
            break;
        case +1:  // Resolved
            PromiseEnqueue(GET_PRIVATE(this, promiseValue),
                           [onResolve, deferred],
                           +1);
            break;
        case -1:  // Rejected
            PromiseEnqueue(GET_PRIVATE(this, promiseValue),
                           [onReject, deferred],
                           -1);
            break;
    }
    // debug代码忽略
    if (DEBUG_IS_ACTIVE) {
        %DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
    }
    return deferred.promise;
}

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


PromiseEnqueue

function PromiseEnqueue(value, tasks, status) {
    var id, name, instrumenting = DEBUG_IS_ACTIVE;
    %EnqueueMicrotask(function() {
        if (instrumenting) {
            %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
        }
        for (var i = 0; i < tasks.length; i += 2) {
            PromiseHandle(value, tasks[i], tasks[i + 1])
        }
        if (instrumenting) {
            %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
        }
    });
    if (instrumenting) {
        id = ++lastMicrotaskId;
        name = status > 0 ? "Promise.resolve" : "Promise.reject";
        %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
    }
}

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


PromiseHandle

function PromiseHandle(value, handler, deferred) {
    try {
        %DebugPushPromise(deferred.promise);
        var result = handler(value);
        if (result === deferred.promise)
            throw MakeTypeError('promise_cyclic', [result]);
        else if (IsPromise(result))
            %_CallFunction(result, deferred.resolve, deferred.reject, PromiseChain);
        else
            deferred.resolve(result);
    } catch (exception) {
        try { deferred.reject(exception); } catch (e) { }
    } finally {
        %DebugPopPromise();
    }
}

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


promise.all

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

function PromiseAll(values) {
    var deferred = %_CallFunction(this, PromiseDeferred);
    var resolutions = [];
    if (!%_IsArray(values)) {
        deferred.reject(MakeTypeError('invalid_argument'));
        return deferred.promise;
    }
    try {
        var count = values.length;
        if (count === 0) {
            deferred.resolve(resolutions);
        } else {
            for (var i = 0; i < values.length; ++i) {
                this.resolve(values[i]).then(
                    (function() {
                        // Nested scope to get closure over current i (and avoid .bind).
                        // TODO(rossberg): Use for-let instead once available.
                        var i_captured = i;
                        return function(x) {
                            resolutions[i_captured] = x;
                            if (--count === 0) deferred.resolve(resolutions);
                        };
                    })(),
                    function(r) { deferred.reject(r) }
                );
            }
        }
    } catch (e) {
        deferred.reject(e)
    }
    return deferred.promise;
}

首先判断传参的合理性,生成一个新的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)),思路如下。

var ref = function (value) {
    if (value && value.then)
        return value;
    return {
        then: function (callback) {
            // 实例化一个新的promise
            var result = defer();
            // 进入宏任务队列
            enqueue(function () {
                result.resolve(callback(value));
            });
            return result.promise;
        }
    };
};


Reference

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

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

来自:https://segmentfault.com/a/1190000019258738


链接: https://fly63.com/article/detial/3409

V8引擎是如何工作?

V8是google开发的JavaScript引擎, 它是 开源的 ,而且是用C++编写的。它用于客户端(Google Chrome)和服务器端(node.js)JavaScript应用程序。V8最初旨在提高Web浏览器中JavaScript执行的性能。为了提升速度,V8将JavaScript代码转换为更高效的机器代码,而不是使用解释器。

Javascript模版引擎mustache.js简介

最近使用ELK的sentinl进行告警配置,sentinl的邮件通知支持mustache。mustache的核心是标签和logic-less.标签: 定义模板的时候,使用了{{name}}、{{#systems}}{{/systems}}标记. 这就是mustache的标签,只不过用{{}}替代了<>

JavaScript 引擎

编写Web代码有时会让开发人员编写一系列字符并像魔术那样神奇,这些字符会在浏览器中变成具体的图像,文字和动作。了解该技术可以帮助开发人员更好地调整他们作为程序员的技能

JavaScript物理引擎之Matter.js与Box2d性能对比

在挑选JavaScript 2D物理引擎的时候,不外乎两种主流的选择:第一种是老牌的Box2D,最开始的版本是C++实现的,后来有了很多种实现,比如flash版本和js版本,第二种是新潮的matter-js,matter-js比较轻量,API和文档都比较有友好。

JS 引擎 V8 发布 v7.4,性能又大幅提高了

JavaScript 引擎 V8 发布了 7.4 版本,目前处于 beta 阶段,正式版将于几个星期后与 Chrome 74 Stable 一起发布。此版本带来了一些新特性,并极大提升了性能。

精读《V8 引擎 Lazy Parsing》

本周精读的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎为了优化性能,做了怎样的尝试吧!这篇文章介绍的优化技术叫 preparser,是通过跳过不必要函数编译的方式优化性能。

JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切

你有没有想过浏览器是如何读取和运行 JavaScript 代码的吗?这看起来很神奇,但你可以学到一些发生在幕后的事情。让我们通过介绍 JavaScript 引擎的精彩世界在这种语言中尽情畅游。

V8引擎-枚举+位运算实现参数配置

基本上从初始化引擎,到Isolate、handleScope、Context一直到编译其实都有记录,但是实在是无从下手。虽说我的博客也没有什么教学意义,但是至少也需要有一个中心和结论。

Node js 视图引擎

Node js 视图引擎就像 Laravel 中的 Blade。其最基本的定义是,视图引擎是帮助我们用比通常更短、更简单的方式编写 HTML 代码并重用的工具。此外,它还可以从服务器端导入数据并渲染最终的 HTML

规则引擎解决方案浅析

用于页面,流程,扩展点实现的选择;输出结果:实现的位置;编排无数的条件积木和行为积木,达到业务逻辑计算,券库存消减的目的;输出结果:商品重计算后的价格;通过订单,售后单,会员等信息编排和判断

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!