Files
cppdraft_translate/cppdraft/exec/task.md
2025-10-25 03:02:53 +03:00

531 lines
22 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[exec.task]
# 33 Execution control library [[exec]](./#exec)
## 33.13 Coroutine utilities [[exec.coro.util]](exec.coro.util#exec.task)
### 33.13.6 execution::task [exec.task]
#### [33.13.6.1](#task.overview) task overview [[task.overview]](task.overview)
[1](#task.overview-1)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7171)
The task class template represents a sender that can
be used as the return type of coroutines[.](#task.overview-1.sentence-1)
The first template parameter T defines the type of the value
completion datum ([[exec.async.ops]](exec.async.ops "33.3Asynchronous operations")) if T is not void[.](#task.overview-1.sentence-2)
Otherwise, there are no value completion datums[.](#task.overview-1.sentence-3)
Inside coroutines returning task<T, E> the operand ofco_return (if any) becomes the argument of set_value[.](#task.overview-1.sentence-4)
The second template parameter Environment is used to customize
the behavior of task[.](#task.overview-1.sentence-5)
#### [33.13.6.2](#task.class) Class template task [[task.class]](task.class)
namespace std::execution {template<class T, class Environment>class [task](#lib:task "33.13.6.2Class template task[task.class]") {// [[task.state]](#task.state "33.13.6.4Class template task::state")template<[receiver](exec.recv.concepts#concept:receiver "33.7.1Receiver concepts[exec.recv.concepts]") Rcvr>class *state*; // *exposition only*public:using sender_concept = sender_t; using completion_signatures = *see below*; using allocator_type = *see below*; using scheduler_type = *see below*; using stop_source_type = *see below*; using stop_token_type = decltype(declval<stop_source_type>().get_token()); using error_types = *see below*; // [[task.promise]](#task.promise "33.13.6.5Class task::promise_­type")class promise_type;
task(task&&) noexcept; ~task(); template<[receiver](exec.recv.concepts#concept:receiver "33.7.1Receiver concepts[exec.recv.concepts]") Rcvr>*state*<Rcvr> connect(Rcvr&& rcvr); private: coroutine_handle<promise_type> *handle*; // *exposition only*};}
[1](#task.class-1)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7216)
task<T, E> models [sender](exec.snd.concepts#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") ([[exec.snd]](exec.snd "33.9Senders"))
if T is void, a reference type, or a cv-unqualified
non-array object type and E is a class type[.](#task.class-1.sentence-1)
Otherwise a program that instantiates the definition of task<T, E> is ill-formed[.](#task.class-1.sentence-2)
[2](#task.class-2)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7223)
The nested types of task template specializations
are determined based on the Environment parameter:
- [(2.1)](#task.class-2.1)
allocator_type is Environment::allocator_type if that [*qualified-id*](expr.prim.id.qual#nt:qualified-id "7.5.5.3Qualified names[expr.prim.id.qual]") is valid and denotes a type,allocator<byte> otherwise[.](#task.class-2.1.sentence-1)
- [(2.2)](#task.class-2.2)
scheduler_type is Environment::scheduler_type if that [*qualified-id*](expr.prim.id.qual#nt:qualified-id "7.5.5.3Qualified names[expr.prim.id.qual]") is valid and denotes a type,task_scheduler otherwise[.](#task.class-2.2.sentence-1)
- [(2.3)](#task.class-2.3)
stop_source_type is Environment::stop_source_type if that [*qualified-id*](expr.prim.id.qual#nt:qualified-id "7.5.5.3Qualified names[expr.prim.id.qual]") is valid and denotes a type,inplace_stop_source otherwise[.](#task.class-2.3.sentence-1)
- [(2.4)](#task.class-2.4)
error_types is Environment::error_types if
that [*qualified-id*](expr.prim.id.qual#nt:qualified-id "7.5.5.3Qualified names[expr.prim.id.qual]") is valid and denotes a type,completion_signatures<set_error_t(exception_ptr)> otherwise[.](#task.class-2.4.sentence-1)
[3](#task.class-3)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7241)
A program is ill-formed if error_types is not a
specialization of completion_signatures<ErrorSigs...> orErrorSigs contains an element which is not of the formset_error_t(E) for some type E[.](#task.class-3.sentence-1)
[4](#task.class-4)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7247)
The type alias completion_signatures is a specialization
of execution::completion_signatures with the template
arguments (in unspecified order):
- [(4.1)](#task.class-4.1)
set_value_t() if T is void,
and set_value_t(T) otherwise;
- [(4.2)](#task.class-4.2)
template arguments of the specialization ofexecution::completion_signatures denoted by error_types;
and
- [(4.3)](#task.class-4.3)
set_stopped_t()[.](#task.class-4.sentence-1)
[5](#task.class-5)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7260)
allocator_type shall meet the *Cpp17Allocator* requirements[.](#task.class-5.sentence-1)
#### [33.13.6.3](#task.members) task members [[task.members]](task.members)
[🔗](#lib:task,constructor)
`task(task&& other) noexcept;
`
[1](#task.members-1)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7271)
*Effects*: Initializes *handle* with exchange(other.*handle*,{})[.](#task.members-1.sentence-1)
[🔗](#lib:task,destructor)
`~task();
`
[2](#task.members-2)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7282)
*Effects*: Equivalent to:if (*handle*)*handle*.destroy();
[🔗](#lib:connect,task)
`template<[receiver](exec.recv.concepts#concept:receiver "33.7.1Receiver concepts[exec.recv.concepts]") Rcvr>
state<Rcvr> connect(Rcvr&& recv);
`
[3](#task.members-3)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7297)
*Preconditions*: bool(*handle*) is true[.](#task.members-3.sentence-1)
[4](#task.members-4)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7301)
*Effects*: Equivalent to:return *state*<Rcvr>(exchange(*handle*, {}), std::forward<Rcvr>(recv));
#### [33.13.6.4](#task.state) Class template task::*state* [[task.state]](task.state)
namespace std::execution {template<class T, class Environment>template<[receiver](exec.recv.concepts#concept:receiver "33.7.1Receiver concepts[exec.recv.concepts]") Rcvr>class task<T, Environment>::*state* { // *exposition only*public:using operation_state_concept = operation_state_t; template<class R>*state*(coroutine_handle<promise_type> h, R&& rr); ~*state*(); void start() & noexcept; private:using *own-env-t* = *see below*; // *exposition only* coroutine_handle<promise_type> *handle*; // *exposition only* remove_cvref_t<Rcvr> *rcvr*; // *exposition only**own-env-t* *own-env*; // *exposition only* Environment *environment*; // *exposition only*};}
[1](#task.state-1)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7336)
The type *own-env-t* is Environment::template env_type<decltype(get_env(declval<Rcvr>()))> if that[*qualified-id*](expr.prim.id.qual#nt:qualified-id "7.5.5.3Qualified names[expr.prim.id.qual]") is valid and denotes a type, env<> otherwise[.](#task.state-1.sentence-1)
[🔗](#lib:task::state,constructor)
`template<class R>
state(coroutine_handle<promise_type> h, R&& rr);
`
[2](#task.state-2)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7347)
*Effects*: Initializes
- [(2.1)](#task.state-2.1)
*handle* with std::move(h);
- [(2.2)](#task.state-2.2)
*rcvr* with std::forward<R>(rr);
- [(2.3)](#task.state-2.3)
*own-env* with *own-env-t*(get_env(*rcvr*)) if that expression
is valid and *own-env-t*() otherwise[.](#task.state-2.3.sentence-1)
If neither of these expressions is valid, the program is ill-formed[.](#task.state-2.3.sentence-2)
- [(2.4)](#task.state-2.4)
*environment* withEnvironment(*own-env*) if that expression is
valid, otherwise Environment(get_env(*rcvr*)) if this expression is valid, otherwise Environment()[.](#task.state-2.4.sentence-1)
If neither of these expressions is valid, the program is ill-formed[.](#task.state-2.4.sentence-2)
[🔗](#lib:task::state,destructor)
`~state();
`
[3](#task.state-3)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7370)
*Effects*: Equivalent to:if (*handle*)*handle*.destroy();
[🔗](#lib:start,task::state)
`void start() & noexcept;
`
[4](#task.state-4)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7384)
*Effects*: Let *prom* be the object *handle*.promise()[.](#task.state-4.sentence-1)
Associates *STATE*(*prom*), *RCVR*(*prom*), and *SCHED*(*prom*) with *this as follows:
- [(4.1)](#task.state-4.1)
*STATE*(*prom*) is *this[.](#task.state-4.1.sentence-1)
- [(4.2)](#task.state-4.2)
*RCVR*(*prom*) is *rcvr*[.](#task.state-4.2.sentence-1)
- [(4.3)](#task.state-4.3)
*SCHED*(*prom*) is the object initialized
with scheduler_type(get_scheduler(get_env(*rcvr*))) if that expression is valid and scheduler_type() otherwise[.](#task.state-4.3.sentence-1)
If neither of these expressions is valid, the program is ill-formed[.](#task.state-4.3.sentence-2)
Let *st* be get_stop_token(get_env(*rcvr*))[.](#task.state-4.sentence-3)
Initializes *prom*.*token* and*prom*.*source* such that
- [(4.4)](#task.state-4.4)
*prom*.*token*.stop_requested() returns*st*.stop_requested();
- [(4.5)](#task.state-4.5)
*prom*.*token*.stop_possible() returns*st*.stop_possible(); and
- [(4.6)](#task.state-4.6)
for types Fn and Init such that both[invocable](concept.invocable#concept:invocable "18.7.2Concept invocable[concept.invocable]")<Fn> and[constructible_from](concept.constructible#concept:constructible_from "18.4.11Concept constructible_­from[concept.constructible]")<Fn, Init> are modeled,stop_token_type::callback_type<Fn> models[*stoppable-callback-for*](stoptoken.concepts#concept:stoppable-callback-for "32.3.3Stop token concepts[stoptoken.concepts]")<Fn, stop_token_type, Init>[.](#task.state-4.sentence-4)
After that invokes *handle*.resume()[.](#task.state-4.sentence-5)
#### [33.13.6.5](#task.promise) Class task::promise_type [[task.promise]](task.promise)
namespace std::execution {template<class T, class Environment>class task<T, Environment>::promise_type {public:template<class... Args> promise_type(const Args&... args);
task get_return_object() noexcept; auto initial_suspend() noexcept; auto final_suspend() noexcept; void uncaught_exception();
coroutine_handle<> unhandled_stopped(); void return_void(); // present only if is_void_v<T> is truetemplate<class V>void return_value(V&& value); // present only if is_void_v<T> is falsetemplate<class E>*unspecified* yield_value(with_error<E> error); template<class A>auto await_transform(A&& a); template<class Sch>auto await_transform(change_coroutine_scheduler<Sch> sch); *unspecified* get_env() const noexcept; template<class... Args>void* operator new(size_t size, Args&&... args); void operator delete(void* pointer, size_t size) noexcept; private:using *error-variant* = *see below*; // *exposition only* allocator_type *alloc*; // *exposition only* stop_source_type *source*; // *exposition only* stop_token_type *token*; // *exposition only* optional<T> *result*; // *exposition only*; present only if is_void_v<T> is false*error-variant* *errors*; // *exposition only*};}
[1](#task.promise-1)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7466)
Let *prom* be an object of promise_type and let *tsk* be the task object
created by *prom*.get_return_object()[.](#task.promise-1.sentence-1)
The description below
refers to objects *STATE*(*prom*),*RCVR*(*prom*),
and *SCHED*(*prom*) associated with *tsk* during evaluation of task::*state*<Rcvr>::start for some receiver Rcvr[.](#task.promise-1.sentence-2)
[2](#task.promise-2)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7478)
*error-variant* is a variant<monostate,
remove_cvref_t<E>...>, with duplicate types removed, where E... are the parameter types of the template arguments of the specialization ofexecution::completion_signatures denoted byerror_types[.](#task.promise-2.sentence-1)
[🔗](#lib:task::promise_type,constructor)
`template<class... Args>
promise_type(const Args&... args);
`
[3](#task.promise-3)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7491)
*Mandates*: The first parameter of type allocator_arg_t (if any) is not
the last parameter[.](#task.promise-3.sentence-1)
[4](#task.promise-4)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7496)
*Effects*: If Args contains an element of type allocator_arg_t then *alloc* is initialized with the corresponding next
element of args[.](#task.promise-4.sentence-1)
Otherwise, *alloc* is initialized with allocator_type()[.](#task.promise-4.sentence-2)
[🔗](#lib:get_return_object,task::promise_type)
`task get_return_object() noexcept;
`
[5](#task.promise-5)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7509)
*Returns*: A task object whose member *handle* iscoroutine_handle<promise_type>::from_promise(*this)[.](#task.promise-5.sentence-1)
[🔗](#lib:initial_suspend,task::promise_type)
`auto initial_suspend() noexcept;
`
[6](#task.promise-6)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7520)
*Returns*: An awaitable object of unspecified type ([[expr.await]](expr.await "7.6.2.4Await")) whose
member functions arrange for
- [(6.1)](#task.promise-6.1)
the calling coroutine to be suspended,
- [(6.2)](#task.promise-6.2)
the coroutine to be resumed on an execution agent of the
execution resource associated with *SCHED*(*this)[.](#task.promise-6.sentence-1)
[🔗](#lib:final_suspend,task::promise_type)
`auto final_suspend() noexcept;
`
[7](#task.promise-7)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7536)
*Returns*: An awaitable object of unspecified type ([[expr.await]](expr.await "7.6.2.4Await")) whose
member functions arrange for the completion of the asynchronous
operation associated with *STATE*(*this) by invoking:
- [(7.1)](#task.promise-7.1)
set_error(std::move(*RCVR*(*this)), std::move(e)) if *errors*.index() is greater than zero ande is the value held by *errors*, otherwise
- [(7.2)](#task.promise-7.2)
set_value(std::move(*RCVR*(*this))) if is_void<T> is true,
and otherwise
- [(7.3)](#task.promise-7.3)
set_value(std::move(*RCVR*(*this)), **result*)[.](#task.promise-7.sentence-1)
[🔗](#lib:yield_value,task::promise_type)
`template<class Err>
auto yield_value(with_error<Err> err);
`
[8](#task.promise-8)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7560)
*Mandates*: std::move(err.error) is convertible to exactly one of theset_error_t argument types of error_types[.](#task.promise-8.sentence-1)
Let *Cerr* be that type[.](#task.promise-8.sentence-2)
[9](#task.promise-9)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7566)
*Returns*: An awaitable object of unspecified type ([[expr.await]](expr.await "7.6.2.4Await")) whose
member functions arrange for the calling coroutine to be suspended
and then completes the asynchronous operation associated with*STATE*(*this) by invoking set_error(std::move(*RCVR*(*this)),*Cerr*(std::move(err.error)))[.](#task.promise-9.sentence-1)
[🔗](#lib:await_transform,task::promise_type)
`template<[sender](exec.snd.concepts#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sender>
auto await_transform(Sender&& sndr) noexcept;
`
[10](#task.promise-10)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7581)
*Returns*: If [same_as](concept.same#concept:same_as "18.4.2Concept same_­as[concept.same]")<inline_scheduler, scheduler_type> is true returns as_awaitable(std::forward<Sender>(sndr), *this);
otherwise returnsas_awaitable(affine_on(std::forward<Sender>(sndr), *SCHED*(*this)), *this)[.](#task.promise-10.sentence-1)
[🔗](#lib:await_transform,task::promise_type_)
`template<class Sch>
auto await_transform(change_coroutine_scheduler<Sch> sch) noexcept;
`
[11](#task.promise-11)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7595)
*Effects*: Equivalent to:return await_transform(just(exchange(*SCHED*(*this), scheduler_type(sch.scheduler))), *this);
[🔗](#lib:uncaught_exception,task::promise_type)
`void uncaught_exception();
`
[12](#task.promise-12)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7608)
*Effects*: If the signature set_error_t(exception_ptr) is not an element
of error_types, calls terminate() ([[except.terminate]](except.terminate "14.6.2The std::terminate function"))[.](#task.promise-12.sentence-1)
Otherwise, stores current_exception() into *errors*[.](#task.promise-12.sentence-2)
[🔗](#lib:unhandled_stopped,task::promise_type)
`coroutine_handle<> unhandled_stopped();
`
[13](#task.promise-13)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7620)
*Effects*: Completes the asynchronous operation associated with *STATE*(*this) by invoking set_stopped(std::move(*RCVR*(*this)))[.](#task.promise-13.sentence-1)
[14](#task.promise-14)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7626)
*Returns*: noop_coroutine()[.](#task.promise-14.sentence-1)
[🔗](#lib:get_env,task::promise_type)
`unspecified get_env() const noexcept;
`
[15](#task.promise-15)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7636)
*Returns*: An object env such that queries are forwarded as follows:
- [(15.1)](#task.promise-15.1)
env.query(get_scheduler) returns scheduler_type(*SCHED*(*this))[.](#task.promise-15.1.sentence-1)
- [(15.2)](#task.promise-15.2)
env.query(get_allocator) returns *alloc*[.](#task.promise-15.2.sentence-1)
- [(15.3)](#task.promise-15.3)
env.query(get_stop_token) returns *token*[.](#task.promise-15.3.sentence-1)
- [(15.4)](#task.promise-15.4)
For any other query q and arguments a... a
call to env.query(q, a...) returns*STATE*(*this)[.](#task.promise-15.4.sentence-1)
environment.query(q, a...) if this expression
is well-formed and forwarding_query(q) is well-formed and is true[.](#task.promise-15.4.sentence-2)
Otherwise env.query(q, a...) is ill-formed[.](#task.promise-15.4.sentence-3)
[🔗](#lib:operator_new,task::promise_type)
`template<class... Args>
void* operator new(size_t size, const Args&... args);
`
[16](#task.promise-16)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7657)
If there is no parameter with type allocator_arg_t then letalloc be allocator_type()[.](#task.promise-16.sentence-1)
Otherwise, let arg_next be the parameter
following the first allocator_arg_t parameter,
and let alloc be allocator_type(arg_next)[.](#task.promise-16.sentence-2)
Let PAlloc be allocator_traits<allocator_type>::template rebind_alloc<U>, where U is an unspecified type
whose size and alignment are both __STDCPP_DEFAULT_NEW_ALIGNMENT__[.](#task.promise-16.sentence-3)
[17](#task.promise-17)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7667)
*Mandates*:
- [(17.1)](#task.promise-17.1)
The first parameter of type allocator_arg_t (if any) is not the last parameter[.](#task.promise-17.1.sentence-1)
- [(17.2)](#task.promise-17.2)
allocator_type(arg_next) is a valid expression if there is a parameter
of type allocator_arg_t[.](#task.promise-17.2.sentence-1)
- [(17.3)](#task.promise-17.3)
allocator_traits<PAlloc>::pointer is a pointer type[.](#task.promise-17.3.sentence-1)
[18](#task.promise-18)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7676)
*Effects*: Initializes an allocator palloc of type PAlloc withalloc[.](#task.promise-18.sentence-1)
Uses palloc to allocate storage for the
smallest array of U sufficient to provide storage for a
coroutine state of size size, and unspecified additional
state necessary to ensure that operator delete can later
deallocate this memory block with an allocator equal to palloc[.](#task.promise-18.sentence-2)
[19](#task.promise-19)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7686)
*Returns*: A pointer to the allocated storage[.](#task.promise-19.sentence-1)
[🔗](#lib:operator_delete,task::promise_type)
`void operator delete(void* pointer, size_t size) noexcept;
`
[20](#task.promise-20)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7696)
*Preconditions*: pointer was returned from an invocation of the above overload
of operator new with a size argument equal to size[.](#task.promise-20.sentence-1)
[21](#task.promise-21)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L7701)
*Effects*: Deallocates the storage pointed to by pointer using an
allocator equal to that used to allocate it[.](#task.promise-21.sentence-1)