~sirn/fanboi2

b578b5aed251410b0483e8bb534d6e7ca46c992e — Kridsada Thanabulpong 5 years ago 061a043
Move from whatwg-fetch to XMLHttpRequest.

Since whatwg-fetch currently doesn't support cancellation and
we need to be able to cancel the request. This commit replace
whatwg-fetch and any code that relies on it to use XHR with
cancellation token.
M assets/app/javascripts/app.ts => assets/app/javascripts/app.ts +0 -3
@@ 1,10 1,7 @@
import 'whatwg-fetch';

import domready = require('domready');
import boardSelector = require('./components/board_selector');
import inlineQuote = require('./components/inline_quote');


domready(function(): void {
    new boardSelector.BoardSelector('.header');
    new inlineQuote.InlineQuote('[data-number]');

M assets/app/javascripts/models/board.ts => assets/app/javascripts/models/board.ts +20 -14
@@ 1,6 1,7 @@
import request = require('../utils/request');
import cancellable = require('../utils/cancellable');
import topic = require('./topic');


export class Board {
    type: string;
    id: number;


@@ 33,25 34,30 @@ export class Board {
        };
    }

    static queryAll(): Promise<Array<Board>> {
        return window.fetch('/api/1.0/boards/').
            then(function(resp: Response): any { return resp.json(); }).
            then(function(boards: any[]): Array<Board> {
                return boards.map(function(board: Object) {
                    return new Board(board);
    static queryAll(
        token?: cancellable.CancellableToken
    ): Promise<Array<Board>> {
        return request.request('GET', '/api/1.0/boards/', token).
            then(function(resp: string): Array<Board> {
                return JSON.parse(resp).map(function(data: Object) {
                    return new Board(data);
                });
            });
    }

    static querySlug(slug: string): Promise<Board> {
        return window.fetch(`/api/1.0/boards/${slug}/`).
            then(function(resp: Response): any { return resp.json(); }).
            then(function(board: any): Board {
                return new Board(board);
    static querySlug(
        slug: string,
        token?: cancellable.CancellableToken
    ): Promise<Board> {
        return request.request('GET', `/api/1.0/boards/${slug}/`, token).
            then(function(resp: string): Board {
                return new Board(JSON.parse(resp));
            });
    }

    getTopics(): Promise<Array<topic.Topic>> {
        return topic.Topic.queryAll(this.slug);
    getTopics(
        token?: cancellable.CancellableToken
    ): Promise<Array<topic.Topic>> {
        return topic.Topic.queryAll(this.slug, token);
    }
}

M assets/app/javascripts/models/post.ts => assets/app/javascripts/models/post.ts +9 -7
@@ 1,3 1,6 @@
import request = require('../utils/request');
import cancellable = require('../utils/cancellable');

export class Post {
    type: string;
    id: number;


@@ 27,7 30,8 @@ export class Post {

    static queryAll(
        topicId: number,
        query?: string
        query?: string,
        token?: cancellable.CancellableToken
    ): Promise<Array<Post>> {
        let entryPoint: string;



@@ 37,12 41,10 @@ export class Post {
            entryPoint = `/api/1.0/topics/${topicId}/posts/`;
        }

        return window.fetch(entryPoint).
            then(function(resp: Response): any { return resp.json(); }).
            then(function(posts: any[]): Array<Post> {
                return posts.map(function(post: Object): Post {
                    return new Post(post);
                });
        return request.request('GET', entryPoint, token).then(function(resp) {
            return JSON.parse(resp).map(function(data: Object): Post {
                return new Post(data);
            });
        });
    }
}

M assets/app/javascripts/models/topic.ts => assets/app/javascripts/models/topic.ts +22 -14
@@ 1,6 1,7 @@
import request = require('../utils/request');
import cancellable = require('../utils/cancellable');
import post = require('./post');


enum Statuses {
    Open,
    Locked,


@@ 37,25 38,32 @@ export class Topic {
        }
    }

    static queryAll(slug: string): Promise<Array<Topic>> {
        return window.fetch(`/api/1.0/boards/${slug}/topics/`).
            then(function(resp: Response): any { return resp.json(); }).
            then(function(topics: any[]): Array<Topic> {
                return topics.map(function(topic: Object) {
                    return new Topic(topic);
    static queryAll(
        slug: string,
        token?: cancellable.CancellableToken
    ): Promise<Array<Topic>> {
        return request.request('GET', `/api/1.0/boards/${slug}/topics/`, token).
            then(function(resp: string): Array<Topic> {
                return JSON.parse(resp).map(function(data: Object) {
                    return new Topic(data);
                });
            });
    }

    static queryId(id: number): Promise<Topic> {
        return window.fetch(`/api/1.0/topics/${id}/`).
            then(function(resp: Response): any { return resp.json(); }).
            then(function(topic: any) {
                return new Topic(topic);
    static queryId(
        id: number,
        token?: cancellable.CancellableToken
    ): Promise<Topic> {
        return request.request('GET', `/api/1.0/topics/${id}/`, token).
            then(function(resp: string) {
                return new Topic(JSON.parse(resp));
            });
    }

    getPosts(query?: string): Promise<Array<post.Post>> {
        return post.Post.queryAll(this.id, query);
    getPosts(
        query?: string,
        token?: cancellable.CancellableToken
    ): Promise<Array<post.Post>> {
        return post.Post.queryAll(this.id, query, token);
    }
}

A assets/app/javascripts/utils/cancellable.ts => assets/app/javascripts/utils/cancellable.ts +7 -0
@@ 0,0 1,7 @@
export interface CancellableToken {
    cancel: void|(() => void);
}

export class CancelToken implements CancellableToken {
    cancel: void
};

A assets/app/javascripts/utils/request.ts => assets/app/javascripts/utils/request.ts +22 -0
@@ 0,0 1,22 @@
import cancellable = require('./cancellable');

export function request(
    method: string,
    url: string,
    token?: cancellable.CancellableToken
): Promise<string> {
    let xhr = new XMLHttpRequest();
    xhr.open(method, url);

    return new Promise(function(resolve, reject) {
        xhr.onload = function() { resolve(xhr.responseText); }
        xhr.onerror = reject;
        if (token) {
            token.cancel = function(): void {
                xhr.abort();
                reject(new Error('Cancelled by token.'));
            }
        }
        xhr.send();
    });
}

M gulpfile.js => gulpfile.js +1 -2
@@ 119,8 119,7 @@ var externalDependencies = [
    'dom4',
    'domready',
    'es6-promise',
    'virtual-dom',
    'whatwg-fetch'
    'virtual-dom'
];

gulp.task('javascripts/app', function(){

M package.json => package.json +1 -2
@@ 26,7 26,6 @@
    "tsify": "^0.12.2",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "virtual-dom": "^2.1.1",
    "whatwg-fetch": "^0.10.1"
    "virtual-dom": "^2.1.1"
  }
}

M typings.json => typings.json +1 -2
@@ 7,7 7,6 @@
  "ambientDependencies": {
    "dom4": "file:typings/vendor/dom4.d.ts",
    "es6-promise": "github:DefinitelyTyped/DefinitelyTyped/es6-promise/es6-promise.d.ts#830e8ebd9ef137d039d5c7ede24a421f08595f83",
    "virtual-dom": "github:DefinitelyTyped/DefinitelyTyped/virtual-dom/virtual-dom.d.ts#df507c636cf0c799a8a20f35af89a3d0caae6c32",
    "whatwg-fetch": "github:DefinitelyTyped/DefinitelyTyped/whatwg-fetch/whatwg-fetch.d.ts#9027703c0bd831319dcdf7f3169f7a468537f448"
    "virtual-dom": "github:DefinitelyTyped/DefinitelyTyped/virtual-dom/virtual-dom.d.ts#df507c636cf0c799a8a20f35af89a3d0caae6c32"
  }
}