summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp
blob: 4d0eaa979a9078a9dc4bd99bc98409bfe06b8d21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
 * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
 * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Debug.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/JobCallback.h>
#include <LibJS/Runtime/Promise.h>
#include <LibJS/Runtime/PromiseJobs.h>
#include <LibJS/Runtime/PromiseReaction.h>

namespace JS {

// 27.2.2.1 NewPromiseReactionJob ( reaction, argument ), https://tc39.es/ecma262/#sec-newpromisereactionjob
static ThrowCompletionOr<Value> run_reaction_job(GlobalObject& global_object, PromiseReaction& reaction, Value argument)
{
    auto& vm = global_object.vm();

    // a. Let promiseCapability be reaction.[[Capability]].
    auto& promise_capability = reaction.capability();

    // b. Let type be reaction.[[Type]].
    auto type = reaction.type();

    // c. Let handler be reaction.[[Handler]].
    auto& handler = reaction.handler();

    Completion handler_result;

    // d. If handler is empty, then
    if (!handler.has_value()) {
        dbgln_if(PROMISE_DEBUG, "run_reaction_job: Handler is empty");

        // i. If type is Fulfill, let handlerResult be NormalCompletion(argument).
        if (type == PromiseReaction::Type::Fulfill) {
            dbgln_if(PROMISE_DEBUG, "run_reaction_job: Reaction type is Type::Fulfill, setting handler result to {}", argument);
            handler_result = normal_completion(argument);
        }
        // ii. Else,
        else {
            // 1. Assert: type is Reject.
            VERIFY(type == PromiseReaction::Type::Reject);

            // 2. Let handlerResult be ThrowCompletion(argument).
            dbgln_if(PROMISE_DEBUG, "run_reaction_job: Reaction type is Type::Reject, throwing exception with argument {}", argument);
            handler_result = throw_completion(argument);
        }
    }
    // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)).
    else {
        dbgln_if(PROMISE_DEBUG, "run_reaction_job: Calling handler callback {} @ {} with argument {}", handler.value().callback.cell()->class_name(), handler.value().callback.cell(), argument);
        MarkedVector<Value> arguments(vm.heap());
        arguments.append(argument);
        handler_result = vm.host_call_job_callback(global_object, handler.value(), js_undefined(), move(arguments));
    }

    // f. If promiseCapability is undefined, then
    if (!promise_capability.has_value()) {
        // i. Assert: handlerResult is not an abrupt completion.
        VERIFY(!handler_result.is_abrupt());

        // ii. Return empty.
        dbgln_if(PROMISE_DEBUG, "run_reaction_job: Reaction has no PromiseCapability, returning empty value");
        // TODO: This can't return an empty value at the moment, because the implicit conversion to Completion would fail.
        //       Change it back when this is using completions (`return normal_completion({})`)
        return js_undefined();
    }

    // g. Assert: promiseCapability is a PromiseCapability Record.

    // h. If handlerResult is an abrupt completion, then
    if (handler_result.is_abrupt()) {
        // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
        auto* reject_function = promise_capability.value().reject;
        dbgln_if(PROMISE_DEBUG, "run_reaction_job: Calling PromiseCapability's reject function @ {}", reject_function);
        return call(global_object, *reject_function, js_undefined(), *handler_result.value());
    }
    // i. Else,
    else {
        // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
        auto* resolve_function = promise_capability.value().resolve;
        dbgln_if(PROMISE_DEBUG, "[PromiseReactionJob]: Calling PromiseCapability's resolve function @ {}", resolve_function);
        return call(global_object, *resolve_function, js_undefined(), *handler_result.value());
    }
}

// 27.2.2.1 NewPromiseReactionJob ( reaction, argument ), https://tc39.es/ecma262/#sec-newpromisereactionjob
PromiseJob create_promise_reaction_job(GlobalObject& global_object, PromiseReaction& reaction, Value argument)
{
    // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called:
    //    See run_reaction_job for "the following steps".
    auto job = [global_object = make_handle(&global_object), reaction = make_handle(&reaction), argument = make_handle(argument)]() mutable {
        return run_reaction_job(*global_object.cell(), *reaction.cell(), argument.value());
    };

    // 2. Let handlerRealm be null.
    Realm* handler_realm { nullptr };

    // 3. If reaction.[[Handler]] is not empty, then
    auto& handler = reaction.handler();
    if (handler.has_value()) {
        // a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])).
        auto get_handler_realm_result = get_function_realm(global_object, *handler->callback.cell());

        // b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]].
        if (!get_handler_realm_result.is_throw_completion()) {
            handler_realm = get_handler_realm_result.release_value();
        } else {
            // c. Else, set handlerRealm to the current Realm Record.
            handler_realm = global_object.vm().current_realm();
        }

        // d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects.
    }

    // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }.
    return { move(job), handler_realm };
}

// 27.2.2.2 NewPromiseResolveThenableJob ( promiseToResolve, thenable, then ), https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
static ThrowCompletionOr<Value> run_resolve_thenable_job(GlobalObject& global_object, Promise& promise_to_resolve, Value thenable, JobCallback& then)
{
    auto& vm = global_object.vm();

    // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve).
    auto [resolve_function, reject_function] = promise_to_resolve.create_resolving_functions();

    // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)).
    dbgln_if(PROMISE_DEBUG, "run_resolve_thenable_job: Calling then job callback for thenable {}", &thenable);
    MarkedVector<Value> arguments(vm.heap());
    arguments.append(Value(&resolve_function));
    arguments.append(Value(&reject_function));
    auto then_call_result = vm.host_call_job_callback(global_object, then, thenable, move(arguments));

    // c. If thenCallResult is an abrupt completion, then
    if (then_call_result.is_error()) {
        // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
        dbgln_if(PROMISE_DEBUG, "run_resolve_thenable_job: then_call_result is an abrupt completion, calling reject function with value {}", *then_call_result.throw_completion().value());
        return call(global_object, &reject_function, js_undefined(), *then_call_result.throw_completion().value());
    }

    // d. Return ? thenCallResult.
    dbgln_if(PROMISE_DEBUG, "run_resolve_thenable_job: Returning then call result {}", then_call_result.value());
    return then_call_result;
}

// 27.2.2.2 NewPromiseResolveThenableJob ( promiseToResolve, thenable, then ), https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
PromiseJob create_promise_resolve_thenable_job(GlobalObject& global_object, Promise& promise_to_resolve, Value thenable, JobCallback then)
{
    // 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])).
    auto get_then_realm_result = get_function_realm(global_object, *then.callback.cell());

    Realm* then_realm;

    // 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]].
    if (!get_then_realm_result.is_throw_completion()) {
        then_realm = get_then_realm_result.release_value();
    } else {
        // 4. Else, let thenRealm be the current Realm Record.
        then_realm = global_object.vm().current_realm();
    }

    // 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects.
    VERIFY(then_realm);

    // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called:
    //    See PromiseResolveThenableJob::call() for "the following steps".
    //    NOTE: This is done out of order, since `then` is moved into the lambda and `then` would be invalid if it was done at the start.
    auto job = [global_object = make_handle(&global_object), promise_to_resolve = make_handle(&promise_to_resolve), thenable = make_handle(thenable), then = move(then)]() mutable {
        return run_resolve_thenable_job(*global_object.cell(), *promise_to_resolve.cell(), thenable.value(), then);
    };

    // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
    return { move(job), then_realm };
}

}