FastLED 3.9.15
Loading...
Searching...
No Matches
fetch.cpp.hpp
Go to the documentation of this file.
1#include "fl/net/http/fetch.h"
2#include "fl/stl/singleton.h"
4#include "fl/task/executor.h"
5#include "fl/stl/unique_ptr.h" // For make_unique
6#include "fl/task/task.h" // For fl::task::every_ms
7#include "fl/net/rpc_scheduler.h" // For fl::net::RpcScheduler
8#include "fl/net/http/fetch_request.h" // For fl::net::http::FetchRequest
9
10// IWYU pragma: begin_keep
11#ifdef FL_IS_WASM
12#include <emscripten.h>
13#include <emscripten/val.h>
14#endif
15// IWYU pragma: end_keep
16
17// Include WASM-specific implementation
18// IWYU pragma: begin_keep
19#include "platforms/wasm/js_fetch.h" // ok platform headers
20// IWYU pragma: end_keep
21
22// Native networking includes
23#if defined(FASTLED_HAS_NETWORKING) && !defined(FL_IS_WASM)
24#include "fl/stl/cstdlib.h" // for atoi
25#include "fl/stl/int.h" // for u16
26
27#ifdef FL_IS_WIN
28 #include "platforms/win/socket_win.h" // ok platform headers // IWYU pragma: keep
29#else
30 #include "platforms/posix/socket_posix.h" // ok platform headers // IWYU pragma: keep
31 #include <fcntl.h> // ok platform headers (for O_NONBLOCK flag) // IWYU pragma: keep
32#include "fl/stl/noexcept.h"
33#endif
34#endif
35
36namespace fl {
37namespace net {
38namespace http {
39
40#ifdef FL_IS_WASM
41// ========== WASM Implementation using JavaScript fetch ==========
42
43
44
45// Promise storage moved to FetchManager singleton
46
47// Use existing WASM fetch infrastructure
48void fetch(const fl::string& url, const FetchCallback& callback) {
49 // Use the existing WASM fetch implementation - no conversion needed since both use fl::response
50 ::fl::wasm_fetch.get(url).response(callback);
51}
52
53// Internal helper to execute a fetch request and return a promise
54fl::task::Promise<Response> execute_fetch_request(const fl::string& url, const FetchOptions& request) {
55 // Create a promise for this request
57
58 // Register with fetch manager to ensure it's tracked
60
61 // Get the actual URL to use (use request URL if provided, otherwise use parameter URL)
62 fl::string fetch_url = request.url().empty() ? url : request.url();
63
64 // Convert our request to the existing WASM fetch system
65 auto wasm_request = ::fl::WasmFetchRequest(fetch_url);
66
67 // Use lambda that captures the promise directly (shared_ptr is safe to copy)
68 // Make the lambda mutable so we can call non-const methods on the captured promise
69 wasm_request.response([promise](const Response& resp) mutable {
70 // Complete the promise directly - no need for double storage
71 if (promise.valid()) {
72 promise.complete_with_value(resp);
73 }
74 });
75
76 return promise;
77}
78
79
80
81#elif defined(FASTLED_HAS_NETWORKING)
82// ========== Native Socket Implementation (POSIX/Windows) ==========
83
84#ifdef FL_IS_WIN
85 #define SOCKET_ERROR_WOULD_BLOCK WSAEWOULDBLOCK
86 #define SOCKET_ERROR_IN_PROGRESS WSAEINPROGRESS
87#else
88 #define SOCKET_ERROR_WOULD_BLOCK EWOULDBLOCK
89 #define SOCKET_ERROR_IN_PROGRESS EINPROGRESS
90#endif
91
92namespace {
93
94// Old blocking implementation removed - now using FetchRequest with fl::task
95
96/*
97// Helper: Perform synchronous HTTP request (DEPRECATED - now async with fl::task)
98response perform_http_request(const fl::string& url, const FetchOptions& request) {
99 ParsedURL parsed = parse_url(url);
100
101 if (!parsed.valid) {
102 response resp(400, "Bad Request");
103 resp.set_body("Invalid URL");
104 return resp;
105 }
106
107 if (parsed.protocol == "https") {
108 response resp(501, "Not Implemented");
109 resp.set_body("HTTPS not supported on this platform");
110 return resp;
111 }
112
113 // Create socket (initialization handled by socket wrapper)
114 int sock = socket(AF_INET, SOCK_STREAM, 0);
115 if (sock < 0) {
116 response resp(500, "Internal Server Error");
117 resp.set_body("Failed to create socket");
118 return resp;
119 }
120
121 // Resolve hostname
122 FL_WARN("[FETCH] Resolving hostname: " << parsed.host);
123 struct hostent* server = gethostbyname(parsed.host.c_str());
124 if (server == nullptr) {
125 close(sock);
126 response resp(500, "Internal Server Error");
127 resp.set_body("Failed to resolve hostname");
128 return resp;
129 }
130
131 // Set socket to non-blocking mode (allows async integration)
132#ifdef FL_IS_WIN
133 u_long mode = 1; // 1 = non-blocking
134 ioctlsocket(sock, FIONBIO, &mode);
135#else
136 int flags = fcntl(sock, F_GETFL, 0);
137 fcntl(sock, F_SETFL, flags | O_NONBLOCK);
138#endif
139
140 // Connect to server
141 struct sockaddr_in server_addr{};
142 server_addr.sin_family = AF_INET;
143 server_addr.sin_port = htons(static_cast<u16>(parsed.port));
144 memcpy(&server_addr.sin_addr.s_addr, server->h_addr, static_cast<size_t>(server->h_length));
145
146 // Non-blocking connect may return EINPROGRESS/EWOULDBLOCK immediately
147 int conn_result = connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
148 if (conn_result < 0) {
149#ifdef FL_IS_WIN
150 int err = WSAGetLastError();
151#else
152 int err = errno;
153#endif
154 if (err != SOCKET_ERROR_IN_PROGRESS && err != SOCKET_ERROR_WOULD_BLOCK) {
155 close(sock);
156 response resp(500, "Internal Server Error");
157 resp.set_body("Failed to connect to server");
158 return resp;
159 }
160 // Connection in progress, wait for it to complete
161 fd_set write_fds;
162 struct timeval timeout;
163 timeout.tv_sec = 5; // 5 second timeout
164 timeout.tv_usec = 0;
165
166 FD_ZERO(&write_fds);
167 FD_SET(sock, &write_fds);
168
169 // Wait for connection with async pumping
170 FL_WARN("[FETCH] Waiting for connection to " << parsed.host << ":" << parsed.port);
171 while (true) {
172 timeout.tv_sec = 0;
173 timeout.tv_usec = 10000; // 10ms
174 int sel_result = select(sock + 1, nullptr, &write_fds, nullptr, &timeout);
175 if (sel_result > 0) {
176 // Check if connection succeeded
177 int sock_err = 0;
178 socklen_t len = sizeof(sock_err);
179 getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&sock_err, &len);
180 if (sock_err != 0) {
181 close(sock);
182 response resp(500, "Internal Server Error");
183 resp.set_body("Failed to connect to server");
184 return resp;
185 }
186 break; // Connection successful
187 } else if (sel_result < 0) {
188 close(sock);
189 response resp(500, "Internal Server Error");
190 resp.set_body("select() failed during connection");
191 return resp;
192 }
193 // Timeout or no data yet - pump async system to allow server to process
194 task::run(0);
195 }
196 }
197
198 // Build HTTP request
199 const auto& opts = request.options();
200 fl::string method = opts.method.empty() ? "GET" : opts.method;
201
202 fl::string http_request;
203 http_request += method + " " + parsed.path + " HTTP/1.0\r\n";
204 http_request += "Host: " + parsed.host + "\r\n";
205 http_request += "Connection: close\r\n";
206
207 // Add custom headers
208 for (const auto& header : opts.headers) {
209 http_request += header.first + ": " + header.second + "\r\n";
210 }
211
212 // Add body if present
213 if (!opts.body.empty()) {
214 http_request += "Content-Length: " + fl::to_string(opts.body.size()) + "\r\n";
215 http_request += "\r\n";
216 http_request += opts.body;
217 } else {
218 http_request += "\r\n";
219 }
220
221 // Send request
222 if (send(sock, http_request.c_str(), static_cast<int>(http_request.size()), 0) < 0) {
223 close(sock);
224 response resp(500, "Internal Server Error");
225 resp.set_body("Failed to send request");
226 return resp;
227 }
228
229 // Read response (non-blocking with async pumping)
230 FL_WARN("[FETCH] Waiting for HTTP response...");
231 fl::string response_data;
232 char buffer[4096];
233 int retries = 0;
234 const int max_retries = 5000; // 5000 * 10ms = 50 seconds timeout
235
236 while (retries < max_retries) {
237 int bytes = recv(sock, buffer, sizeof(buffer), 0);
238 if (bytes > 0) {
239 // Got data - append and reset retry counter
240 response_data.append(buffer, static_cast<size_t>(bytes));
241 retries = 0; // Reset on successful read
242 } else if (bytes == 0) {
243 // Connection closed by server
244 break;
245 } else {
246 // Error or would block
247#ifdef FL_IS_WIN
248 int err = WSAGetLastError();
249#else
250 int err = errno;
251#endif
252 if (err == SOCKET_ERROR_WOULD_BLOCK) {
253 // No data available yet - pump async system to allow server to process
254 task::run(0);
255 retries++;
256
257 // Small delay to avoid busy-waiting
258#ifdef FL_IS_WIN
259 Sleep(10); // 10ms
260#else
261 usleep(10000); // 10ms
262#endif
263 } else {
264 // Real error
265 close(sock);
266 response resp(500, "Internal Server Error");
267 resp.set_body("Failed to read response");
268 return resp;
269 }
270 }
271 }
272
273 close(sock);
274
275 if (retries >= max_retries) {
276 response resp(500, "Internal Server Error");
277 resp.set_body("Timeout reading response");
278 return resp;
279 }
280
281 // Parse HTTP response
282 size_t header_end = response_data.find("\r\n\r\n");
283 if (header_end == fl::string::npos) {
284 response resp(500, "Internal Server Error");
285 resp.set_body("Invalid HTTP response");
286 return resp;
287 }
288
289 fl::string header_section = response_data.substr(0, header_end);
290 fl::string body = response_data.substr(header_end + 4);
291
292 // Parse status line
293 size_t first_line_end = header_section.find("\r\n");
294 fl::string status_line = header_section.substr(0, first_line_end);
295
296 // Extract status code (format: "HTTP/1.1 200 OK")
297 size_t code_start = status_line.find(' ');
298 if (code_start == fl::string::npos) {
299 response resp(500, "Internal Server Error");
300 resp.set_body("Invalid status line");
301 return resp;
302 }
303
304 size_t code_end = status_line.find(' ', code_start + 1);
305 fl::string code_str = status_line.substr(code_start + 1, code_end - code_start - 1);
306 int status_code = atoi(code_str.c_str());
307
308 fl::string status_text = (code_end != fl::string::npos)
309 ? status_line.substr(code_end + 1)
310 : "";
311
312 // Create response object
313 response resp(status_code, status_text);
314 resp.set_body(body);
315
316 // Parse headers
317 size_t header_start = first_line_end + 2;
318 while (header_start < header_section.size()) {
319 size_t line_end = header_section.find("\r\n", header_start);
320 if (line_end == fl::string::npos) break;
321
322 fl::string header_line = header_section.substr(header_start, line_end - header_start);
323 size_t colon = header_line.find(':');
324 if (colon != fl::string::npos) {
325 fl::string name = header_line.substr(0, colon);
326 fl::string value = header_line.substr(colon + 1);
327 // Trim leading space from value
328 if (!value.empty() && value[0] == ' ') {
329 value = value.substr(1);
330 }
331 // Normalize header name to lowercase for case-insensitive lookup (RFC 2616)
332 for (size_t i = 0; i < name.size(); ++i) {
333 if (name[i] >= 'A' && name[i] <= 'Z') {
334 name[i] = name[i] + ('a' - 'A');
335 }
336 }
337 resp.set_header(name, value);
338 }
339
340 header_start = line_end + 2;
341 }
342
343 return resp;
344}
345*/
346
347} // anonymous namespace
348
349void fetch(const fl::string& url, const FetchCallback& callback) {
350 // Use async execute_fetch_request and attach callback to promise
352 .then([callback](const Response& resp) {
353 callback(resp);
354 })
355 .catch_([callback](const fl::task::Error& err) {
356 // On error, return 500 response
357 Response resp(500, "Internal Server Error");
358 resp.set_body(err.message);
359 callback(resp);
360 });
361}
362
363// Internal helper to execute a fetch request and return a promise
364fl::task::Promise<Response> execute_fetch_request(const fl::string& url, const FetchOptions& request) {
365 // Create promise for this request
367
368 // Register promise with FetchManager for tracking
370
371 // Create shared FetchRequest (managed by task lambda)
372 auto fetch_req = fl::make_shared<FetchRequest>(url, request, promise);
373
374 // Create self-canceling task (stored in shared_ptr for lambda capture)
375 auto task_ptr = fl::make_shared<fl::task::Handle>();
376
377 *task_ptr = fl::task::every_ms(1) // Update every 1ms
378 .then([fetch_req, task_ptr]() {
379 // Pump the fetch state machine
380 fetch_req->update();
381
382 // Self-cancel when done
383 if (fetch_req->is_done()) {
384 task_ptr->cancel();
385 }
386 })
387 .catch_([promise](const fl::task::Error& e) mutable {
388 // Task error - reject promise if not already completed
389 if (promise.valid() && !promise.is_completed()) {
390 promise.complete_with_error(e);
391 }
392 });
393
394 // Add to scheduler
396
397 return promise;
398}
399
400#else
401// ========== True Stub Implementation (no networking) ==========
402
403void fetch(const fl::string& url, const FetchCallback& callback) {
404 (void)url;
405 Response resp(501, "Not Implemented");
406 resp.set_text("HTTP fetch not supported on this platform");
407 callback(resp);
408}
409
411 (void)request;
412 FL_WARN("HTTP fetch is not supported on this platform. URL: " << url);
413 Response error_response(501, "Not Implemented");
414 error_response.set_body("HTTP fetch is not available on this platform.");
415 return fl::task::Promise<Response>::resolve(error_response);
416}
417
418#endif
419
420// ========== Engine Events Integration ==========
421
422
423
424// ========== Promise-Based API Implementation ==========
425
427public:
432 // Listener base class automatically removes itself
434 }
435
436 void onEndFrame() override {
437 // Update all async tasks (fetch, timers, etc.) at the end of each frame
438 fl::task::run(0);
439 }
440};
441
445
447 // Auto-register with async system and engine listener on first promise
448 if (mActivePromises.empty()) {
450
451 if (!mEngineListener) {
454 }
455 }
456
457 mActivePromises.push_back(promise);
458}
459
461 // Update all active promises first
462 for (auto& promise : mActivePromises) {
463 if (promise.valid()) {
464 promise.update();
465 }
466 }
467
468 // Then clean up completed/invalid promises in a separate pass
470
471 // Auto-unregister from async system when no more promises
472 if (mActivePromises.empty()) {
474
475 if (mEngineListener) {
477 mEngineListener.reset();
478 }
479 }
480}
481
483 return !mActivePromises.empty();
484}
485
487 return mActivePromises.size();
488}
489
491 return mActivePromises.size();
492}
493
495 // Rebuild vector without completed promises
497 for (const auto& promise : mActivePromises) {
498 if (promise.valid() && !promise.is_completed()) {
499 active_promises.push_back(promise);
500 }
501 }
502 mActivePromises = fl::move(active_promises);
503}
504
505// WASM promise management methods removed - no longer needed
506// Promises are now handled directly via shared_ptr capture in callbacks
507
508// ========== Public API Functions ==========
509
511 // Create a new request with GET method
512 FetchOptions get_request(url, RequestOptions("GET"));
513
514 // Apply any additional options from the provided request
515 const auto& opts = request.options();
516 get_request.timeout(opts.timeout_ms);
517 for (const auto& header : opts.headers) {
518 get_request.header(header.first, header.second);
519 }
520 if (!opts.body.empty()) {
521 get_request.body(opts.body);
522 }
523
524 return execute_fetch_request(url, get_request);
525}
526
528 // Create a new request with POST method
529 FetchOptions post_request(url, RequestOptions("POST"));
530
531 // Apply any additional options from the provided request
532 const auto& opts = request.options();
533 post_request.timeout(opts.timeout_ms);
534 for (const auto& header : opts.headers) {
535 post_request.header(header.first, header.second);
536 }
537 if (!opts.body.empty()) {
538 post_request.body(opts.body);
539 }
540
541 return execute_fetch_request(url, post_request);
542}
543
545 // Create a new request with PUT method
546 FetchOptions put_request(url, RequestOptions("PUT"));
547
548 // Apply any additional options from the provided request
549 const auto& opts = request.options();
550 put_request.timeout(opts.timeout_ms);
551 for (const auto& header : opts.headers) {
552 put_request.header(header.first, header.second);
553 }
554 if (!opts.body.empty()) {
555 put_request.body(opts.body);
556 }
557
558 return execute_fetch_request(url, put_request);
559}
560
562 // Create a new request with DELETE method
563 FetchOptions delete_request(url, RequestOptions("DELETE"));
564
565 // Apply any additional options from the provided request
566 const auto& opts = request.options();
567 delete_request.timeout(opts.timeout_ms);
568 for (const auto& header : opts.headers) {
569 delete_request.header(header.first, header.second);
570 }
571 if (!opts.body.empty()) {
572 delete_request.body(opts.body);
573 }
574
575 return execute_fetch_request(url, delete_request);
576}
577
579 // Create a new request with HEAD method
580 FetchOptions head_request(url, RequestOptions("HEAD"));
581
582 // Apply any additional options from the provided request
583 const auto& opts = request.options();
584 head_request.timeout(opts.timeout_ms);
585 for (const auto& header : opts.headers) {
586 head_request.header(header.first, header.second);
587 }
588 if (!opts.body.empty()) {
589 head_request.body(opts.body);
590 }
591
592 return execute_fetch_request(url, head_request);
593}
594
596 // Create a new request with OPTIONS method
597 FetchOptions options_request(url, RequestOptions("OPTIONS"));
598
599 // Apply any additional options from the provided request
600 const auto& opts = request.options();
601 options_request.timeout(opts.timeout_ms);
602 for (const auto& header : opts.headers) {
603 options_request.header(header.first, header.second);
604 }
605 if (!opts.body.empty()) {
606 options_request.body(opts.body);
607 }
608
609 return execute_fetch_request(url, options_request);
610}
611
613 // Create a new request with PATCH method
614 FetchOptions patch_request(url, RequestOptions("PATCH"));
615
616 // Apply any additional options from the provided request
617 const auto& opts = request.options();
618 patch_request.timeout(opts.timeout_ms);
619 for (const auto& header : opts.headers) {
620 patch_request.header(header.first, header.second);
621 }
622 if (!opts.body.empty()) {
623 patch_request.body(opts.body);
624 }
625
626 return execute_fetch_request(url, patch_request);
627}
628
630 // Create a FetchOptions with the provided options
631 FetchOptions request(url, options);
632
633 // Use the helper function to execute the request
634 return execute_fetch_request(url, request);
635}
636
638 // Legacy function - use fl::task::run() for new code
639 // This provides backwards compatibility for existing code
640 fl::task::run(0);
641}
642
646
647// ========== Response Class Method Implementations ==========
648
650 if (!mJsonParsed) {
651 if (is_json() || mBody.find("{") != fl::string::npos || mBody.find("[") != fl::string::npos) {
653 } else {
654 FL_WARN("Response is not JSON: " << mBody);
655 mCachedJson = fl::json(nullptr); // Not JSON content
656 }
657 mJsonParsed = true;
658 }
659
660 return mCachedJson.has_value() ? *mCachedJson : fl::json(nullptr);
661}
662
663} // namespace http
664} // namespace net
665} // namespace fl
static void removeListener(Listener *listener) FL_NOEXCEPT
static void addListener(Listener *listener, int priority=0) FL_NOEXCEPT
static T & instance() FL_NOEXCEPT
Definition singleton.h:41
~FetchEngineListener() FL_NOEXCEPT override
bool has_active_tasks() const override
Check if this runner has active tasks.
fl::unique_ptr< FetchEngineListener > mEngineListener
Definition fetch.h:265
size_t active_task_count() const override
Get number of active tasks (for debugging/monitoring)
fl::vector< fl::task::Promise< Response > > mActivePromises
Definition fetch.h:264
static FetchManager & instance()
void register_promise(const fl::task::Promise< Response > &promise)
fl::size active_requests() const
void update() override
Update this runner (called during task pumping)
Internal fetch manager for promise tracking.
Definition fetch.h:248
FetchOptions & header(const fl::string &name, const fl::string &value)
Add header.
Definition fetch.h:208
FetchOptions & body(const fl::string &data)
Set request body.
Definition fetch.h:214
FetchOptions & timeout(int timeout_ms)
Set timeout in milliseconds.
Definition fetch.h:227
const RequestOptions & options() const
Get the options for this request.
Definition fetch.h:236
Fetch options builder (fluent interface)
Definition fetch.h:195
bool is_json() const
Check if response appears to contain JSON content.
Definition fetch.h:122
void set_text(const fl::string &body)
Definition fetch.h:135
fl::json parse_json_body() const
Parse JSON from response body with error handling.
Definition fetch.h:152
fl::json json() const
Response body parsed as JSON (JavaScript-like API)
fl::optional< fl::json > mCachedJson
Definition fetch.h:148
void set_body(const fl::string &body)
Definition fetch.h:136
HTTP response class (unified interface)
Definition fetch.h:78
static constexpr fl::size npos
Definition string.h:195
void register_runner(Runner *r)
Register a runner.
static Executor & instance()
void unregister_runner(Runner *r)
Unregister a runner.
Handle & then(function< void()> on_then) FL_NOEXCEPT
Definition task.cpp.hpp:276
static Promise< T > resolve(const T &value) FL_NOEXCEPT
Create a resolved Promise with value.
Definition promise.h:67
static Promise< T > create() FL_NOEXCEPT
Create a pending Promise.
Definition promise.h:61
Promise class that provides fluent .then() and .catch_() semantics This is a lightweight wrapper arou...
Definition promise.h:58
static Scheduler & instance()
int add_task(Handle t)
Definition url.h:15
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
Task executor — runs registered task runners and manages the run loop.
Unified HTTP fetch API for FastLED (cross-platform)
#define FL_WARN(X)
Definition log.h:276
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition s16x16x4.h:28
void fetch_update()
Legacy manual update for fetch promises (use fl::task::run() for new code)
fl::task::Promise< Response > fetch_delete(const fl::string &url, const FetchOptions &request)
HTTP DELETE request.
fl::function< void(const Response &)> FetchCallback
Callback type for simple fetch responses (backward compatible)
Definition fetch.h:181
fl::task::Promise< Response > fetch_http_options(const fl::string &url, const FetchOptions &request)
HTTP OPTIONS request.
fl::task::Promise< Response > fetch_patch(const fl::string &url, const FetchOptions &request)
HTTP PATCH request.
fl::task::Promise< Response > fetch_post(const fl::string &url, const FetchOptions &request)
HTTP POST request.
fl::task::Promise< Response > fetch_head(const fl::string &url, const FetchOptions &request)
HTTP HEAD request.
fl::task::Promise< Response > execute_fetch_request(const fl::string &url, const FetchOptions &request)
Internal helper to execute a fetch request and return a promise.
fl::task::Promise< Response > fetch_get(const fl::string &url, const FetchOptions &request)
HTTP GET request.
fl::task::Promise< Response > fetch_request(const fl::string &url, const RequestOptions &options)
Generic request with options (like fetch(url, options))
void fetch(const fl::string &url, const FetchCallback &callback)
Make an HTTP GET request (cross-platform, backward compatible)
fl::task::Promise< Response > fetch_put(const fl::string &url, const FetchOptions &request)
HTTP PUT request.
fl::size fetch_active_requests()
Get number of active requests.
Handle every_ms(int interval_ms)
Definition task.cpp.hpp:320
void run(fl::u32 microseconds, ExecFlags flags)
Run selected task subsystems.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
Request options (matches JavaScript fetch RequestInit)
Definition fetch.h:184
fl::string message
Definition promise.h:40