Merge pull request #12808 from tkrotoff/whatwg-fetch

whatwg-fetch: allow Body.json() typed result
This commit is contained in:
Andy 2016-12-13 17:06:28 -08:00 committed by GitHub
commit 0a8e31370e
6 changed files with 323 additions and 104 deletions

View File

@ -6,9 +6,9 @@
/// <reference types="whatwg-streams" />
interface Window {
fetch(url: RequestInfo, init?: RequestInit): Promise<Response>;
fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
}
declare var fetch: typeof window.fetch;
declare let fetch: typeof window.fetch;
declare type HeadersInit = Headers | string[][] | { [key: string]: string };
declare class Headers {
@ -16,7 +16,7 @@ declare class Headers {
append(name: string, value: string): void;
delete(name: string): void;
get(name: string): string; // | null; (TS 2.0 strict null check)
get(name: string): string | null;
has(name: string): boolean;
set(name: string, value: string): void;
@ -29,32 +29,35 @@ declare class Headers {
}
declare type BodyInit = Blob | ArrayBufferView | ArrayBuffer | FormData /* | URLSearchParams */ | string;
declare type ResponseBodyInit = BodyInit | ReadableStream;
interface Body {
bodyUsed: boolean;
readonly bodyUsed: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<any>;
json<T>(): Promise<T>;
text(): Promise<string>;
}
declare type RequestInfo = Request | string;
declare class Request {
constructor(input: RequestInfo, init?: RequestInit);
method: string;
url: string;
headers: Headers;
constructor(input: RequestInfo, init?: RequestInit);
type: RequestType
destination: RequestDestination;
referrer: string;
referrerPolicy: ReferrerPolicy;
mode: RequestMode;
credentials: RequestCredentials;
cache: RequestCache;
redirect: RequestRedirect;
integrity: string;
readonly method: string;
readonly url: string;
readonly headers: Headers;
readonly type: RequestType
readonly destination: RequestDestination;
readonly referrer: string;
readonly referrerPolicy: ReferrerPolicy;
readonly mode: RequestMode;
readonly credentials: RequestCredentials;
readonly cache: RequestCache;
readonly redirect: RequestRedirect;
readonly integrity: string;
readonly keepalive: boolean;
clone(): Request;
}
@ -70,7 +73,7 @@ interface RequestInit {
cache?: RequestCache;
redirect?: RequestRedirect;
integrity?: string;
window?: any;
window?: null; // can only be set to null
}
type RequestType = "" | "audio" | "font" | "image" | "script" | "style" | "track" | "video";
@ -79,27 +82,30 @@ type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors";
type RequestCredentials = "omit" | "same-origin" | "include";
type RequestCache = "default" | "no-store" | "reload" | "no-cache" | "force-cache" | "only-if-cached";
type RequestRedirect = "follow" | "error" | "manual";
type ReferrerPolicy = "" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url";
declare class Response {
constructor(body?: BodyInit, init?: ResponseInit);
constructor(body?: ResponseBodyInit, init?: ResponseInit);
static error(): Response;
static redirect(url: string, status?: number): Response;
type: ResponseType;
url: string;
redirected: boolean;
status: number;
ok: boolean;
statusText: string;
headers: Headers;
body: ReadableStream; // | null;
trailer: Promise<Headers>;
readonly type: ResponseType;
readonly url: string;
readonly redirected: boolean;
readonly status: number;
readonly ok: boolean;
readonly statusText: string;
readonly headers: Headers;
readonly body: ReadableStream | null;
readonly trailer: Promise<Headers>;
clone(): Response;
}
interface Response extends Body {}
interface ResponseInit {
status?: number;
statusText?: string;

View File

@ -3,7 +3,7 @@
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"strictNullChecks": false,
"strictNullChecks": true,
"baseUrl": "../",
"typeRoots": [
"../"

View File

@ -68,4 +68,43 @@ function handlePromise(promise: Promise<Response>) {
}).then((text) => {
console.log(text);
});
}
}
function test_Body_json() {
interface FooBar {
foo: string;
bar: string;
}
fetch('http://test.com')
.then(response => response.json<FooBar>())
.then(fooBar => {
console.log(fooBar.foo);
console.log(fooBar.bar);
});
fetch('http://test.com')
.then(response => <Promise<FooBar>> response.json())
.then(fooBar => {
console.log(fooBar.foo);
console.log(fooBar.bar);
});
fetch('http://test.com')
.then(response => response.json() as Promise<FooBar>)
.then(fooBar => {
console.log(fooBar.foo);
console.log(fooBar.bar);
});
fetch('http://test.com')
.then(response => response.json())
.then(fooBar => {
// fooBar is of type any, not of type FooBar
// FIXME Was behaving properly with TypeScript 2.0.10, not anymore with 2.1.4
// See Wrong type with Promise chaining and 2.1.4 https://github.com/Microsoft/TypeScript/issues/12409
//console.log(fooBar.foo);
//console.log(fooBar.bar);
});
}

View File

@ -15,11 +15,18 @@ interface ReadableByteStreamSource {
cancel?(reason: string): void | Promise<void>;
type: "bytes";
autoAllocateChunkSize?: number;
}
interface QueuingStrategy {
highWaterMark?: number;
size?(chunk: ArrayBufferView): number;
highWaterMark?: number;
}
interface PipeOptions {
preventClose?: boolean;
preventAbort?: boolean;
preventCancel?: boolean;
}
declare class ReadableStream {
@ -31,8 +38,8 @@ declare class ReadableStream {
cancel(reason: string): Promise<void>;
getReader(): ReadableStreamDefaultReader;
getReader({ mode }: { mode: "byob" }): ReadableStreamBYOBReader;
pipeThrough<T extends ReadableStream>({ writable, readable }: { writable: WritableStream, readable: T }): T;
pipeTo(dest: WritableStream, { preventClose, preventAbort, preventCancel }: { preventClose?: boolean, preventAbort?: boolean, preventCancel?: boolean }): Promise<void>;
pipeThrough<T extends ReadableStream>({ writable, readable }: { writable: WritableStream, readable: T }, options?: PipeOptions): T;
pipeTo(dest: WritableStream, options?: PipeOptions): Promise<void>;
tee(): [ReadableStream, ReadableStream];
}
@ -49,7 +56,7 @@ declare class ReadableStreamDefaultReader {
declare class ReadableStreamBYOBReader {
constructor(stream: ReadableStream);
closed: Promise<boolean>;
closed: Promise<void>;
cancel(reason: string): Promise<void>;
read(view: ArrayBufferView): Promise<IteratorResult<ArrayBufferView>>;
@ -88,8 +95,8 @@ declare class ReadableStreamBYOBRequest {
interface WritableStreamSink {
start?(controller: WritableStreamDefaultController): void | Promise<void>;
write?(chunk: any): void | Promise<void>;
close?(): void | Promise<void>;
write?(chunk: any, controller?: WritableStreamDefaultController): void | Promise<any>;
close?(controller: WritableStreamDefaultController): void | Promise<void>;
abort?(reason: string): void | Promise<void>;
}
@ -106,7 +113,7 @@ declare class WritableStreamDefaultWriter {
constructor(stream: WritableStream);
closed: Promise<void>;
desiredSize: number;
desiredSize: number | null;
ready: Promise<void>;
abort(reason: string): Promise<void>;
@ -124,11 +131,11 @@ declare class WritableStreamDefaultController {
declare class ByteLengthQueuingStrategy {
constructor({ highWaterMark }: { highWaterMark: number });
size(chunk: ArrayBufferView): number;
size(chunk: ArrayBufferView): number | undefined;
}
declare class CountQueuingStrategy {
constructor({ highWaterMark }: { highWaterMark: number });
size(): number; // 1;
}
size(): 1;
}

View File

@ -3,7 +3,7 @@
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"strictNullChecks": false,
"strictNullChecks": true,
"baseUrl": "../",
"typeRoots": [
"../"

View File

@ -1,20 +1,39 @@
function makeReadableWebSocketStream(url: string, protocols: string[]) {
const ws = new WebSocket(url, protocols);
ws.binaryType = "arraybuffer";
/// <reference types="node" />
return new ReadableStream({
start(controller) {
ws.onmessage = event => controller.enqueue(event.data);
ws.onclose = () => controller.close();
ws.onerror = () => controller.error(new Error("The WebSocket errored!"));
},
// Examples taken from https://streams.spec.whatwg.org/#creating-examples
cancel() {
ws.close();
}
});
// 8.1. A readable stream with an underlying push source (no backpressure support)
{
function makeReadableWebSocketStream(url: string, protocols: string | string[]) {
const ws = new WebSocket(url, protocols);
ws.binaryType = "arraybuffer";
return new ReadableStream({
start(controller) {
ws.onmessage = event => controller.enqueue(event.data);
ws.onclose = () => controller.close();
ws.onerror = () => controller.error(new Error("The WebSocket errored!"));
},
cancel() {
ws.close();
}
});
}
const writableStream = new WritableStream();
const webSocketStream = makeReadableWebSocketStream("wss://example.com:443/", "protocol");
webSocketStream.pipeTo(writableStream)
.then(() => console.log("All data successfully written!"))
.catch(e => console.error("Something went wrong!", e));
}
// 8.2. A readable stream with an underlying push source and backpressure support
function makeReadableBackpressureSocketStream(host: string, port: number) {
const socket = createBackpressureSocket(host, port);
@ -49,6 +68,9 @@ function makeReadableBackpressureSocketStream(host: string, port: number) {
function createBackpressureSocket(host: string, port: number): any { };
}
// 8.3. A readable byte stream with an underlying push source (no backpressure support)
const DEFAULT_CHUNK_SIZE = 65536;
function makeUDPSocketStream(host: string, port: number) {
@ -93,7 +115,100 @@ function makeUDPSocketStream(host: string, port: number) {
function createUDPSocket(host: string, port: number): any { };
}
function makeWritableWebSocketStream(url: string, protocols: string[]) {
// 8.4. A readable stream with an underlying pull source
//const fs = require("pr/fs"); // https://github.com/jden/pr
interface fs {
open(path: string | Buffer, flags: string | number): Promise<number>;
read(fd: number, buffer: Buffer, offset: number, length: number, position: number): Promise<number>;
write(fd: number, buffer: Buffer, offset: number, length: number): Promise<number>;
close(fd: number): Promise<void>;
}
let fs: fs;
{
const CHUNK_SIZE = 1024;
function makeReadableFileStream(filename: string) {
let fd: number;
let position = 0;
return new ReadableStream({
start() {
return fs.open(filename, "r").then(result => {
fd = result;
});
},
pull(controller) {
const buffer = new ArrayBuffer(CHUNK_SIZE);
return fs.read(fd, <any> buffer, 0, CHUNK_SIZE, position).then(bytesRead => {
if (bytesRead === 0) {
return fs.close(fd).then(() => controller.close());
} else {
position += bytesRead;
controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
}
});
},
cancel() {
return fs.close(fd);
}
});
}
}
// 8.5. A readable byte stream with an underlying pull source
{
//const fs = require("pr/fs"); // https://github.com/jden/pr
const DEFAULT_CHUNK_SIZE = 1024;
function makeReadableByteFileStream(filename: string) {
let fd: number;
let position = 0;
return new ReadableStream({
type: "bytes",
start() {
return fs.open(filename, "r").then(result => {
fd = result;
});
},
pull(controller) {
// Even when the consumer is using the default reader, the auto-allocation
// feature allocates a buffer and passes it to us via byobRequest.
const v = controller.byobRequest.view;
return fs.read(fd, <any> v.buffer, v.byteOffset, v.byteLength, position).then(bytesRead => {
if (bytesRead === 0) {
return fs.close(fd).then(() => controller.close());
} else {
position += bytesRead;
controller.byobRequest.respond(bytesRead);
}
});
},
cancel() {
return fs.close(fd);
},
autoAllocateChunkSize: DEFAULT_CHUNK_SIZE
});
}
}
// 8.6. A writable stream with no backpressure or success signals
function makeWritableWebSocketStream(url: string, protocols: string | string[]) {
const ws = new WebSocket(url, protocols);
return new WritableStream({
@ -117,71 +232,123 @@ function makeWritableWebSocketStream(url: string, protocols: string[]) {
});
}
function streamifyWebSocket(url: string, protocol: string) {
const ws = new WebSocket(url, protocol);
ws.binaryType = "arraybuffer";
{
const readableStream = new ReadableStream();
return {
readable: new ReadableStream(new WebSocketSource(ws)),
writable: new WritableStream(new WebSocketSink(ws))
};
const webSocketStream = makeWritableWebSocketStream("wss://example.com:443/", "protocol");
readableStream.pipeTo(webSocketStream)
.then(() => console.log("All data successfully written!"))
.catch(e => console.error("Something went wrong!", e));
}
class WebSocketSource implements ReadableStreamSource {
private _ws: WebSocket
constructor(ws: WebSocket) {
this._ws = ws;
}
// 8.7. A writable stream with backpressure and success signals
start(controller: ReadableStreamDefaultController) {
this._ws.onmessage = event => controller.enqueue(event.data);
this._ws.onclose = () => controller.close();
{
//const fs = require("pr/fs"); // https://github.com/jden/pr
this._ws.addEventListener("error", () => {
controller.error(new Error("The WebSocket errored!"));
function makeWritableFileStream(filename: string) {
let fd: number;
return new WritableStream({
start() {
return fs.open(filename, "w").then(result => {
fd = result;
});
},
write(chunk) {
return fs.write(fd, chunk, 0, chunk.length);
},
close() {
return fs.close(fd);
}
});
}
cancel() {
this._ws.close();
}
const fileStream = makeWritableFileStream("/example/path/on/fs.txt");
const writer = fileStream.getWriter();
writer.write("To stream, or not to stream\n");
writer.write("That is the question\n");
writer.close()
.then(() => console.log("chunks written and stream closed successfully!"))
.catch(e => console.error(e));
}
class WebSocketSink implements WritableStreamSink {
private _ws: WebSocket
constructor(ws: WebSocket) {
this._ws = ws;
// 8.8. A { readable, writable } stream pair wrapping the same underlying resource
{
function streamifyWebSocket(url: string, protocol: string) {
const ws = new WebSocket(url, protocol);
ws.binaryType = "arraybuffer";
return {
readable: new ReadableStream(new WebSocketSource(ws)),
writable: new WritableStream(new WebSocketSink(ws))
};
}
start(controller: WritableStreamDefaultController) {
this._ws.addEventListener("error", () => {
controller.error(new Error("The WebSocket errored!"));
});
class WebSocketSource implements ReadableStreamSource {
private _ws: WebSocket;
return new Promise<void>(resolve => this._ws.onopen = () => resolve());
}
constructor(ws: WebSocket) {
this._ws = ws;
}
write(chunk: any) {
this._ws.send(chunk);
}
start(controller: ReadableStreamDefaultController) {
this._ws.onmessage = event => controller.enqueue(event.data);
this._ws.onclose = () => controller.close();
close() {
return new Promise<void>((resolve, reject) => {
this._ws.onclose = () => resolve();
this._ws.addEventListener("error", () => {
controller.error(new Error("The WebSocket errored!"));
});
}
cancel() {
this._ws.close();
});
}
}
class WebSocketSink implements WritableStreamSink {
private _ws: WebSocket;
constructor(ws: WebSocket) {
this._ws = ws;
}
start(controller: WritableStreamDefaultController) {
this._ws.addEventListener("error", () => {
controller.error(new Error("The WebSocket errored!"));
});
return new Promise<void>(resolve => this._ws.onopen = () => resolve());
}
write(chunk: any) {
this._ws.send(chunk);
}
close() {
return new Promise<void>((resolve, reject) => {
this._ws.onclose = () => resolve();
this._ws.close();
});
}
}
const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol");
const writer = streamyWS.writable.getWriter();
const reader = streamyWS.readable.getReader();
writer.write("Hello");
writer.write("web socket!");
reader.read().then(({ value, done }) => {
console.log("The web socket says: ", value);
});
}
const streamyWS = streamifyWebSocket("wss://example.com:443/", "protocol");
const writer = streamyWS.writable.getWriter();
const reader = streamyWS.readable.getReader();
writer.write("Hello");
writer.write("web socket!");
reader.read().then(({ value, done }) => {
console.log("The web socket says: ", value);
});