Skip to content

Commit 603a278

Browse files
authored
feat(ext/fetch): fetch method (WIP)
feat(ext/fetch): fetch method
2 parents e892a2d + 4a1ca5b commit 603a278

File tree

6 files changed

+131
-9
lines changed

6 files changed

+131
-9
lines changed

examples/fetch.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const foo = async () => {
2+
try {
3+
const res = await fetch("https://developer.mozilla.org");
4+
console.log(res);
5+
} catch (e) {
6+
console.error(e);
7+
}
8+
};
9+
10+
foo();

runtime/src/ext/fetch/fetch/mod.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
/**
6+
* Implementation of the fetch API for Andromeda
7+
* Based on: https://developer.mozilla.org/ja/docs/Web/API/Window/fetch
8+
* Spec: https://fetch.spec.whatwg.org/#fetch-method/
9+
*/
10+
11+
type RequestInfo = Request | URL;
12+
13+
/** The fetch(input, init) method steps are: */
14+
const fetch = (input: RequestInfo, init = undefined) => {
15+
// 1. Let p be a new promise.
16+
let p = createDeferredPromise();
17+
18+
// 2. Let requestObject be the result of invoking the initial value
19+
// of Request as constructor with input and init as arguments.
20+
// If this throws an exception, reject p with it and return p.
21+
let request: any;
22+
23+
try {
24+
// 3. Let request be requestObject’s request.
25+
// @ts-ignore deno lint stuff
26+
request = new Request(input, init);
27+
} catch (e) {
28+
p.reject(e);
29+
return p.promise;
30+
}
31+
32+
// 4. If requestObject’s signal is aborted, then:
33+
// if (request.signal.aborted) {
34+
// 1. Abort the fetch() call with p, request, null, and
35+
// requestObject’s signal’s abort reason.
36+
//
37+
// TODO: abortFetch
38+
//
39+
// 2. Return p.
40+
// return p.promise;
41+
// }
42+
43+
// 5. Let globalObject be request’s client’s global object.
44+
// const globalObject = request.client.globalObject;
45+
46+
// 6. If globalObject is a ServiceWorkerGlobalScope object,
47+
// then set request’s service-workers mode to "none".
48+
// if (globalObject?.constructor?.name === "ServiceWorkerGlobalScope") {
49+
// request.serviceWorkers = "none";
50+
// }
51+
52+
// 7. Let responseObject be null.
53+
let responseObject = null;
54+
55+
// 8. Let relevantRealm be this’s relevant realm.
56+
// 9. Let locallyAborted be false.
57+
// NOTE: This lets us reject promises with predictable timing,
58+
// when the request to abort comes from the same thread as
59+
// the call to fetch.
60+
let locallyAborted = false;
61+
62+
// 10. Let controller be null.
63+
let controller = null;
64+
65+
// TODO: abort controller
66+
// 11. Add the following abort steps to requestObject’s signal:
67+
// 1. Set locallyAborted to true.
68+
// 2. Assert: controller is non-null.
69+
// 3. Abort controller with requestObject’s signal’s abort reason.
70+
// 4. Abort the fetch() call with p, request, responseObject,
71+
// and requestObject’s signal’s abort reason.
72+
73+
// 12. Set controller to the result of calling fetch given request
74+
// and processResponse given response being these steps:
75+
// 1. If locallyAborted is true, then abort these steps.
76+
// 2. If response’s aborted flag is set, then:
77+
// 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s serialized abort reason and relevantRealm.
78+
// 2. Abort the fetch() call with p, request, responseObject, and deserializedError.
79+
// 3. Abort these steps.
80+
// 3. If response is a network error, then reject p with a TypeError and abort these steps.
81+
// 4. Set responseObject to the result of creating a Response object, given response, "immutable", and relevantRealm.
82+
// 5. Resolve p with responseObject.
83+
84+
// 13. Return p.
85+
return p;
86+
};
87+
88+
(globalThis as unknown as { fetch: typeof fetch }).fetch = fetch;
89+
90+
function createDeferredPromise() {
91+
let res: any;
92+
let rej: any;
93+
const promise = new Promise((resolve, reject) => {
94+
res = resolve;
95+
rej = reject;
96+
});
97+
98+
return { promise, resolve: res, reject: rej };
99+
}

runtime/src/ext/fetch/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ mod headers;
66
mod request;
77
mod response;
88

9+
use andromeda_core::Extension;
910
pub use headers::*;
1011
pub use request::*;
1112
pub use response::*;
13+
14+
#[derive(Default)]
15+
pub struct FetchExt;
16+
17+
impl FetchExt {
18+
pub fn new_extension() -> Extension {
19+
Extension {
20+
name: "fetch",
21+
ops: vec![],
22+
storage: None,
23+
files: vec![include_str!("./fetch/mod.ts")],
24+
}
25+
}
26+
}

runtime/src/ext/fetch/request/mod.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ class Request {
103103
[bodySymbol]: any = null;
104104

105105
/** https://fetch.spec.whatwg.org/#request-class */
106-
constructor(input: RequestInfo, init: RequestInit = { __proto__: null } as any) {
106+
constructor(
107+
input: RequestInfo,
108+
init: RequestInit = { __proto__: null } as any,
109+
) {
107110
// 1. Let request be null.
108111
let request: any = null;
109112

@@ -396,13 +399,6 @@ class Request {
396399
// 42. Set this’s request’s body to finalBody.
397400
request.body = finalBody;
398401

399-
const url = request.url;
400-
console.log("url", url);
401-
const method = request.method;
402-
console.log("method", method);
403-
const credentials = request.credentials;
404-
console.log("credentials", credentials);
405-
406402
this[requestSymbol] = request;
407403
}
408404

runtime/src/ext/local_storage/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ impl LocalStorageExt {
131131
args: ArgumentsList,
132132
gc: GcScope<'gc, '_>,
133133
) -> JsResult<'gc, Value<'gc>> {
134-
let persistent = if args.len() > 0 {
134+
let persistent = if !args.is_empty() {
135135
match args.get(0) {
136136
Value::Boolean(b) => b,
137137
_ => true, // default to localStorage

runtime/src/recommended.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use nova_vm::ecmascript::execution::agent::{GcAgent, RealmRoot};
88
use crate::{
99
//BroadcastChannelExt,
1010
ConsoleExt,
11+
FetchExt,
1112
FsExt,
1213
HeadersExt,
1314
ProcessExt,
@@ -31,6 +32,7 @@ pub fn recommended_extensions() -> Vec<Extension> {
3132
// BroadcastChannelExt::new_extension(),
3233
RequestExt::new_extension(),
3334
ResponseExt::new_extension(),
35+
FetchExt::new_extension(),
3436
#[cfg(feature = "canvas")]
3537
crate::CanvasExt::new_extension(),
3638
#[cfg(feature = "crypto")]

0 commit comments

Comments
 (0)