JSCore运行时(二)-Promise的实现

Promise

首先,我们看下JS里面promise的实现,注意这里带有@符号前缀的方法有部分是由对应的C++方法实现的,可以在工程里根据方法名字搜索
先看下构造方法

@globalPrivate
function initializePromise(executor)
{
    "use strict";

    if (typeof executor !== 'function')
        @throwTypeError("Promise constructor takes a function argument");
    //设置promiseState为pending
    @putByIdDirectPrivate(this, "promiseState", @promiseStatePending);
    //初始化promiseReactions数组
    @putByIdDirectPrivate(this, "promiseReactions", []);
    @putByIdDirectPrivate(this, "promiseIsHandled", false);
   //创建解决函数,也就是resolve和reject
    var resolvingFunctions = @createResolvingFunctions(this);
    try {
        executor(resolvingFunctions.@resolve, resolvingFunctions.@reject);
    } catch (error) {
        return resolvingFunctions.@reject.@call(@undefined, error);
    }

    return this;
}

@globalPrivate
function createResolvingFunctions(promise)
{
    "use strict";

    var alreadyResolved = false;

    function @resolve(resolution) {
        if (alreadyResolved)
            return @undefined;
        alreadyResolved = true;
       //如果resolve的函数就是promise本身,则报错
        if (resolution === promise)
            return @rejectPromise(promise, new @TypeError("Resolve a promise with itself"));

        if (!@isObject(resolution))
            return @fulfillPromise(promise, resolution);
        //尝试获取then
        var then;
        try {
            then = resolution.then;
        } catch (error) {
            //出现错误,则调用reject
            return @rejectPromise(promise, error);
        }

        if (typeof then !== 'function')
            return @fulfillPromise(promise, resolution);
        //获取成功的话,往任务队列中追加一个promiseResolveThenableJob任务
        @enqueueJob(@promiseResolveThenableJob, [promise, resolution, then]);

        return @undefined;
    }

    function @reject(reason) {
        if (alreadyResolved)
            return @undefined;
        alreadyResolved = true;

        return @rejectPromise(promise, reason);
    }

    return { @resolve, @reject };
}

@globalPrivate
//这个任务的作用就是调用promise的then函数,并捕获异常
function promiseResolveThenableJob(promiseToResolve, thenable, then)
{
    "use strict";

    var resolvingFunctions = @createResolvingFunctions(promiseToResolve);

    try {
        return then.@call(thenable, resolvingFunctions.@resolve, resolvingFunctions.@reject);
    } catch (error) {
        return resolvingFunctions.@reject.@call(@undefined, error);
    }
}

再看下fullfill和reject

@globalPrivate
function triggerPromiseReactions(state, reactions, argument)
{
    "use strict";
    //把所有的reactions(resolve和reject的组合)取出来放入队列执行
    for (var index = 0, length = reactions.length; index < length; ++index)
        @enqueueJob(@promiseReactionJob, [state, reactions[index], argument]);
}

@globalPrivate
function rejectPromise(promise, reason)
{
    "use strict";

    var reactions = @getByIdDirectPrivate(promise, "promiseReactions");
    @putByIdDirectPrivate(promise, "promiseResult", reason);
    @putByIdDirectPrivate(promise, "promiseReactions", @undefined);
    @putByIdDirectPrivate(promise, "promiseState", @promiseStateRejected);
    //这里引入了inspector,对promise做性能检测
    @InspectorInstrumentation.promiseRejected(promise, reason, reactions);

    if (!@getByIdDirectPrivate(promise, "promiseIsHandled"))
        @hostPromiseRejectionTracker(promise, @promiseRejectionReject);

    @triggerPromiseReactions(@promiseStateRejected, reactions, reason);
}

@globalPrivate
function fulfillPromise(promise, value)
{
    "use strict";

    var reactions = @getByIdDirectPrivate(promise, "promiseReactions");
    @putByIdDirectPrivate(promise, "promiseResult", value);
    @putByIdDirectPrivate(promise, "promiseReactions", @undefined);
    @putByIdDirectPrivate(promise, "promiseState", @promiseStateFulfilled);
    //这里引入了inspector,对promise做性能检测
    @InspectorInstrumentation.promiseFulfilled(promise, value, reactions);

    @triggerPromiseReactions(@promiseStateFulfilled, reactions, value);
}

在PromiseProtoType.js里面我们可以看到 then方法的具体实现

function then(onFulfilled, onRejected)
{
    "use strict";

    if (!@isPromise(this))
        @throwTypeError("|this| is not a object");

    var constructor = @speciesConstructor(this, @Promise);

    var resultCapability = @newPromiseCapability(constructor);

    if (typeof onFulfilled !== "function")
        onFulfilled = function (argument) { return argument; };

    if (typeof onRejected !== "function")
        onRejected = function (argument) { throw argument; };
    //把resolve和reject包装一下
    var reaction = @newPromiseReaction(resultCapability, onFulfilled, onRejected);

    var state = @getByIdDirectPrivate(this, "promiseState");
     //如果还是处于pending状态,则把新创建的reaction加入到reactions里面
    if (state === @promiseStatePending) {
        var reactions = @getByIdDirectPrivate(this, "promiseReactions");
        @putByValDirect(reactions, reactions.length, reaction);
    } else {//其他状态,rejected或者fullfilled
        if (state === @promiseStateRejected && !@getByIdDirectPrivate(this, "promiseIsHandled"))
            @hostPromiseRejectionTracker(this, @promiseRejectionHandle);
        //这时候Promise本身的任务已经执行完了,调用then则直接把任务加到队列里执行
        @enqueueJob(@promiseReactionJob, [state, reaction, @getByIdDirectPrivate(this, "promiseResult")]);
    }

    @putByIdDirectPrivate(this, "promiseIsHandled", true);

    return resultCapability.@promise;
}

Promise.then()调用了enqueueJob ,而enqueueJob实际则先创建了一个microTask,然后调用了queueMicroTask加到任务队列里面。

static EncodedJSValue JSC_HOST_CALL enqueueJob(ExecState* exec)
{
    VM& vm = exec->vm();
    JSGlobalObject* globalObject = exec->lexicalGlobalObject();

    JSValue job = exec->argument(0);
    JSValue arguments = exec->argument(1);
    ASSERT(arguments.inherits<JSArray>(vm));

    globalObject->queueMicrotask(createJSJob(vm, job, jsCast<JSArray*>(arguments)));

    return JSValue::encode(jsUndefined());
}

MicroTask

这里解释下MicroTask
MicroTask是一个抽象类,定义了run接口,用于执行一小段任务。

而JSJobMicroTask是他的具体实现,继承自microTask,实现了run接口

class JSJobMicrotask final : public Microtask {
public:
    JSJobMicrotask(VM& vm, JSValue job, JSArray* arguments)
    {
        m_job.set(vm, job);
        m_arguments.set(vm, arguments);
    }

    virtual ~JSJobMicrotask()
    {
    }

private:
    void run(ExecState*) override;

    Strong<Unknown> m_job;
    Strong<JSArray> m_arguments;
};

Ref<Microtask> createJSJob(VM& vm, JSValue job, JSArray* arguments)
{
    return adoptRef(*new JSJobMicrotask(vm, job, arguments));
}

void JSJobMicrotask::run(ExecState* exec)
{
    VM& vm = exec->vm();
    //获取作用域
    auto scope = DECLARE_CATCH_SCOPE(vm);

    CallData handlerCallData;
    CallType handlerCallType = getCallData(vm, m_job.get(), handlerCallData);
    ASSERT(handlerCallType != CallType::None);
   //检查参数合法性
    MarkedArgumentBuffer handlerArguments;
    for (unsigned index = 0, length = m_arguments->length(); index < length; ++index) {
        JSValue arg = m_arguments->JSArray::get(exec, index);
        CLEAR_AND_RETURN_IF_EXCEPTION(scope, handlerArguments.overflowCheckNotNeeded());
        handlerArguments.append(arg);
    }
    if (UNLIKELY(handlerArguments.hasOverflowed()))
        return;
    //执行调用
    profiledCall(exec, ProfilingReason::Microtask, m_job.get(), handlerCallType, handlerCallData, jsUndefined(), handlerArguments);
    scope.clearException();
}

}

run的作用: 获取scope,检查参数合法性,调用profiledCall执行任务
run在drainMicroTask中被调用。

MicroTaskQueue

任务队列,是VM的一个成员变量 ,名称叫m_microTaskQueue, 相关的方法有两个,一个是queueMicroTask,一个是drainMicroTask.

void VM::queueMicrotask(JSGlobalObject& globalObject, Ref<Microtask>&& task)
{ 
   //把任务加入队列末尾
    m_microtaskQueue.append(std::make_unique<QueuedTask>(*this, &globalObject, WTFMove(task)));
}
void VM::drainMicrotasks()
{
  //把队列中所有任务取出来执行(每次从队列头部取出任务)
    while (!m_microtaskQueue.isEmpty())
        m_microtaskQueue.takeFirst()->run();
}

drainMicroTask调用的地方有几个:

  1. jsc.cpp 里面,比如runWithOptions、runInteractive、runJSC,调用jsCore时执行。
  2. promiseDeferredTimer的doWork函数里面,执行某个延时操作时调用

jsc.cpp

jsc.cpp是javascriptCore工程的main文件,即程序启动的入口文件。调用runJSC就是开始运行javascriptCore。runJSC里面开启了runloop,在mac os和ios上就是CFRunloop,其他平台上也是类似的运行循环。在runJSC里面还启动了一个PromiseDeferredTimer,用来处理延迟操作

template<typename Func>
int runJSC(CommandLine options, bool isWorker, const Func& func)
{
    Worker worker(Workers::singleton());
    
    VM& vm = VM::create(LargeHeap).leakRef();
    int result;
    bool success = true;
    GlobalObject* globalObject = nullptr;
    {
        JSLockHolder locker(vm);

        if (options.m_profile && !vm.m_perBytecodeProfiler)
            vm.m_perBytecodeProfiler = std::make_unique<Profiler::Database>(vm);

        globalObject = GlobalObject::create(vm, GlobalObject::createStructure(vm, jsNull()), options.m_arguments);
        globalObject->setRemoteDebuggingEnabled(options.m_enableRemoteDebugging);
        func(vm, globalObject, success);
        //取出队列中的所有任务并执行
        vm.drainMicrotasks();
    }
    //开启runloop,执行定时任务
    vm.promiseDeferredTimer->runRunLoop();
    {
        JSLockHolder locker(vm);
        if (options.m_interactive && success)
            runInteractive(globalObject);
    }
   //......
}

JSRunLoopTimer

JScore利用CFRunloop的timer执行定时操作,跟iOS的NSTimer类似,有scheduleTimer、cancelTimer等方法。内部持有了CFRunLoopTimerRef类型的一个对象m_timer及CFRunloopRef类型的m_runLoop。
promiseDeferredTimer就是继承于JSRunLoopTimer.

class PromiseDeferredTimer : public JSRunLoopTimer {
public:
    using Base = JSRunLoopTimer;

    PromiseDeferredTimer(VM&);

    void doWork() override;

    void addPendingPromise(JSPromiseDeferred*, Vector<Strong<JSCell>>&& dependencies);
    JS_EXPORT_PRIVATE bool hasPendingPromise(JSPromiseDeferred* ticket);
    JS_EXPORT_PRIVATE bool hasDependancyInPendingPromise(JSPromiseDeferred* ticket, JSCell* dependency);
    // JSPromiseDeferred should handle canceling when the promise is resolved or rejected.
    bool cancelPendingPromise(JSPromiseDeferred*);

    typedef std::function<void()> Task;
    void scheduleWorkSoon(JSPromiseDeferred*, Task&&);

    void stopRunningTasks() { m_runTasks = false; }

    JS_EXPORT_PRIVATE void runRunLoop();

private:
    HashMap<JSPromiseDeferred*, Vector<Strong<JSCell>>> m_pendingPromises;
    Lock m_taskLock;
    bool m_runTasks { true };
    bool m_shouldStopRunLoopWhenAllPromisesFinish { false };
    bool m_currentlyRunningTask { false };
    Vector<std::tuple<JSPromiseDeferred*, Task>> m_tasks;
};

} // namespace JSC

promiseDeferredTimer的doWork是在timerDidFire函数中调用的。用来执行具体的任务,可以看到,这里为了避免多线程同时执行任务,还做了加锁操作,这也提现了javascript单线程的特点。

void PromiseDeferredTimer::doWork()
{
    ASSERT(m_vm->currentThreadIsHoldingAPILock());
    m_taskLock.lock();
    cancelTimer();
    if (!m_runTasks) {
        m_taskLock.unlock();
        return;
    }

    while (!m_tasks.isEmpty()) {
        JSPromiseDeferred* ticket;
        Task task;
        std::tie(ticket, task) = m_tasks.takeLast();
        dataLogLnIf(PromiseDeferredTimerInternal::verbose, "Doing work on promise: ", RawPointer(ticket));

        // We may have already canceled these promises.
        if (m_pendingPromises.contains(ticket)) {
            // Allow tasks we run now to schedule work.
            m_currentlyRunningTask = true;
            m_taskLock.unlock(); 

            task();
            m_vm->drainMicrotasks();

            m_taskLock.lock();
            m_currentlyRunningTask = false;
        }
    }

    if (m_pendingPromises.isEmpty() && m_shouldStopRunLoopWhenAllPromisesFinish) {
#if USE(CF)
        CFRunLoopStop(m_runLoop.get());
#else
        RunLoop::current().stop();
#endif
    }

    m_taskLock.unlock();
}

同样继承自JSRunLoopTimer的还有StopIfNecessaryTimer、IncrementalSweeper和GCActivityCallback,这些都是跟JS内存管理相关的类,这里就不展开讲了,后续会专门开一篇讲垃圾回收的实现。

VM中有一个m_runLoopTimers数组维护JSRunLoopTimer。每次调用registerRunLoopTimer就会往m_runLoopTimers中添加一个JSRunLoopTimer。

void VM::registerRunLoopTimer(JSRunLoopTimer* timer)
{
    ASSERT(runLoop());
    ASSERT(!m_runLoopTimers.contains(timer));
    m_runLoopTimers.add(timer);
    timer->setRunLoop(runLoop());
}

void VM::unregisterRunLoopTimer(JSRunLoopTimer* timer)
{
    ASSERT(m_runLoopTimers.contains(timer));
    m_runLoopTimers.remove(timer);
    timer->setRunLoop(nullptr);
}

这里runloop()是获取当前线程的runloop
我们知道,iOS的runloop是跟线程一一对应的,那么JS的runloop既然是使用了CFRunloop,那也是跟线程一一对应,那么也就不难理解JS里面的任务为什么是串行的了,因为VM里面的runloop只有一个,只能在一个事件循环中按顺序执行。对于Promise是如此,对于其他的异步任务,如Timer、async/await等也是如此。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270