簡體   English   中英

使用TypeScript和Promises異步加載/卸載內容

[英]Async Loading/Unloading of content using TypeScript and Promises

我創建了一個框架,用於使用TypeScript,Knockout,TypeScript的通用承諾( https://github.com/pragmatrix/Promise )和異步( https://github.com/caolan/async )來異步加載/卸載內容。

雖然邏輯工作正常,並且事件觸發並以正確的順序發生,但在NavigationItem加載時,UI不會使用新選擇進行更新,並且不會在新項目上開始加載。 誰能知道為什么會這樣嗎?

邏輯的核心在NavigationItem類中:

export class NavigationItem {
    constructor(public Data: INavigationData) {
        this.data = ko.observable(Data);
        this.data.subscribe(n => Data = n);
        this.status = ko.observable(NavigationItemStatus.Unloaded);
        this.isLoading = ko.computed(() => this.status() == NavigationItemStatus.Loading);
        this.isLoaded = ko.computed(() => this.status() == NavigationItemStatus.Loaded);
        this.isUnloaded = ko.computed(() => this.status() == NavigationItemStatus.Unloaded);
        this.isUnloading = ko.computed(() => this.status() == NavigationItemStatus.Unloading);
    }
    public data: KnockoutObservable<INavigationData>;
    public status: KnockoutObservable<NavigationItemStatus>;
    public isLoading: KnockoutComputed<boolean>;
    public isLoaded: KnockoutComputed<boolean>;
    public isUnloading: KnockoutComputed<boolean>;
    public isUnloaded: KnockoutComputed<boolean>;
    public closed: Lind.Events.ITypedEvent<NavigationItem> = new Lind.Events.TypedEvent();
    public navigationItemAdded: Lind.Events.ITypedEvent<NavigationItem> = new Lind.Events.TypedEvent();
    private queue: AsyncQueue<boolean> = async.queue((s, c) => {
        if (s)
            this.loadWorker().done(() => c());
        else
            this.unloadWorker().done(() => c());
    }, 1);
    load() : Promise<boolean>{
        var d = defer<boolean>();
        this.queue.push(true, () => d.resolve(true));
        return d.promise();
    }
    unload() : Promise<boolean>{
        var d = defer<boolean>();
        this.queue.push(false, () => d.resolve(true));
        return d.promise();
    }
    private unloadWorker(): Promise<boolean> {
        var d = defer<boolean>();
        this.doUnload().done(s => this.onUnloaded(s, d));
        this.onUnloading();
        return d.promise();
    }
    private loadWorker(): Promise<boolean>{
        var d = defer<boolean>();
        if (this.isLoaded())
        {
            this.unload();
            this.load();
            d.resolve(false);
        }
        else {
            this.doLoad().done(s => this.onLoaded(s, d));
            this.onLoading();
        }
        return d.promise();
    }
    private onLoaded(loadStatus: boolean, promise: P.Deferred<boolean>) {
        this.status(NavigationItemStatus.Loaded);
        promise.resolve(loadStatus);
    }
    private onUnloaded(unloadStatus: boolean, promise: P.Deferred<boolean>) {
        this.status(NavigationItemStatus.Unloaded);
        promise.resolve(unloadStatus);
    }
    private onLoading() {
        this.status(NavigationItemStatus.Loading);
    }
    private onUnloading() {
        this.status(NavigationItemStatus.Unloading);
    }
    doLoad(): Promise<boolean> {
        var d = defer<boolean>();
        d.resolve(true);
        return d.promise();
    }
    doUnload(): Promise<boolean> {
        var d = defer<boolean>();
        d.resolve(true);
        return d.promise();
    }
    close() {
        if(this.status() != NavigationItemStatus.Unloaded)
            this.unload();
        this.closed.trigger(this);
    }
    addNavigationItem(navigationItem : NavigationItem) {
        this.navigationItemAdded.trigger(navigationItem);
    }
}

調用load()時,它將使加載工作程序排隊,而調用unload()時,將使卸載工作程序排隊,該隊列的並發性為1。NavigationItemCollection類擴展了NavigationItem,公開了一個可觀察的數組,並實現了doLoad和doUnload。

export class NavigationItemCollection<T> extends NavigationItem {
    constructor(data: INavigationData) {
        super(data);
        this.items = ko.observableArray<T>();
    }
    public items: KnockoutObservableArray<T>;
    doLoad(): Promise<boolean> {
        var d = defer<boolean>();
        super.doLoad().done(() => {
            this.getItems().done(i => {
                if (i != null) {
                    for (var k: number = 0; k < i.length; k++) {
                        this.items.push(i[k]);
                    }
                }
                d.resolve(true);
            });
        });
        return d.promise(); 
    }
    doUnload(): Promise<boolean> {
        var d = defer<boolean>();
        super.doUnload().done(() => {
            this.items.removeAll();
            d.resolve(true);
        });
        return d.promise();
    }
    getItems(): Promise<T[]> {
        var d = defer<T[]>();
        d.resolve(null);
        return d.promise();
    }
}

然后,RepositoryNavigationItem類實現NavigationItemCollection並實現getItems()。

export class RepositoryNavigationItem<TViewModel, TEntity> extends ViewModels.Navigation.NavigationItemCollection<TViewModel>{
    constructor(data: ViewModels.Navigation.INavigationData, public Repository: Northwind.Repository.IRepositoryGeneric<TEntity>) {
        super(data);
    }
    getItems(): Promise<TViewModel[]> {
        var d = defer<TViewModel[]>();
        this.Repository.GetAll().done(i => {
            var vms: TViewModel[] = [];
            if (i != null) {
                for (var k: number = 0; k < i.length; k++) {
                    vms.push(this.createViewModel(i[k]));
                }
            }
            d.resolve(vms);
        });
        return d.promise();
    }
    createViewModel(entity : TEntity): TViewModel {
        return null;
    }
}
export class ProductsNavigationItem extends RepositoryNavigationItem<Northwind.Product, Northwind.IProduct>{
    createViewModel(entity: Northwind.IProduct): Northwind.Product {
        return Northwind.Product.Create(entity);
    }
}

存儲庫的實現如下:

export class Repository<TEntity> implements IRepositoryGeneric<TEntity>{
    constructor(public ServiceLocation: string) { }
    GetAll(): Promise<TEntity[]> {
        var d = defer<TEntity[]>();
        $.ajax({
            type: "GET",
            url: this.ServiceLocation + "GetAll",
            success: data => d.resolve(<TEntity[]>data),
            error: err => d.resolve(null)
        });
        return d.promise();
    }
}

然后,MainWindowViewModel實例化NavigationItems(使用IoC依賴項注入),並在選擇和取消選擇NavigationItems時控制加載/卸載控制流。

export class MainWindowViewModel {
    constructor(private Container: Lind.IoC.IContainer, navigationData: ViewModels.Navigation.INavigationData[]) {
        this.navigationItems = ko.observableArray<ViewModels.Navigation.NavigationItem>();
        this.selectedNavigationItem = ko.observable<ViewModels.Navigation.NavigationItem>();
        this.selectedNavigationItemType = ko.computed(() => {
            var navItem = this.selectedNavigationItem();
            if (navItem != null)
                return navItem.data().Name;
            return "Loading";
        });
        this.selectedNavigationItem.subscribe(n => {
            if (n != null)
                n.unload();
        }, this, "beforeChange");
        this.selectedNavigationItem.subscribe(n => {
            if(n != null)
                n.load();
        });
        for (var i: number = 0; i < navigationData.length; i++) {
            var navItem = Container.Resolve<ViewModels.Navigation.NavigationItem>(typeof ViewModels.Navigation.NavigationItem, navigationData[i].Name,
                [new Lind.IoC.ConstructorParameterFactory("data", () => navigationData[i])]);
            navItem.closed.add(this.onNavigationItemClosed);
            navItem.navigationItemAdded.add(this.onNavigationItemAdded);
            this.navigationItems.push(navItem);
        }
        this.selectedNavigationItem(this.navigationItems.peek()[0]);
    }
    public navigationItems: KnockoutObservableArray<ViewModels.Navigation.NavigationItem>;
    public selectedNavigationItem: KnockoutObservable<ViewModels.Navigation.NavigationItem>;
    public selectedNavigationItemType: KnockoutComputed<string>;
    private onNavigationItemClosed(item: ViewModels.Navigation.NavigationItem) {
        item.closed.remove(this.onNavigationItemClosed);
        item.navigationItemAdded.remove(this.onNavigationItemAdded);
        this.navigationItems.remove(item);
        if (this.selectedNavigationItem() == item)
            this.selectedNavigationItem(this.navigationItems.peek()[0]);
    }
    private onNavigationItemAdded(item: ViewModels.Navigation.NavigationItem) {
        item.navigationItemAdded.add(this.onNavigationItemAdded);
        item.closed.add(this.onNavigationItemClosed);
        this.navigationItems.push(item);
        this.selectedNavigationItem(item);
    }
}

視圖如下:

<div id="rightNav" style="float:left">
    <ul data-bind="foreach: navigationItems">
        <li>
            <div data-bind="style:{ background: isLoading() == true ? 'yellow' : (isLoaded() == true ? 'green' : (isUnloading() == true ? 'gray' : (isUnloaded() == true ? 'white' : 'red')))}">
                <span><a data-bind="text:data().DisplayName, click: $parent.selectedNavigationItem"></a><button data-bind="visible:data().IsCloseable == true, click: close" >x</button></span>
            </div>
        </li>
    </ul>
</div>
<div id="leftContent" data-bind="template: { name: selectedNavigationItemType(), data: selectedNavigationItem }"></div>
<script type="text/html" id="Products">
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Supplier</th>
                <th>Category</th>
                <th>Unit Price</th>
                <th>Units in Stock</th>
                <th>Discontinued</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: items">
            <tr>
                <td><span data-bind="text:productName" /></td>
                <td><span data-bind="text: supplier().companyName" /></td>
                <td><span data-bind="text: category().categoryName" /></td>
                <td><span data-bind="text: unitPrice" /></td>
                <td><span data-bind="text: unitsInStock" /></td>
                <td><input type="checkbox" data-bind="checked: discontinued" disabled="disabled" /></td>
            </tr>
        </tbody>
    </table>
</script>

我添加了一個模擬存儲庫,並使用模擬視圖對其進行了測試:

module Northwind.Repository.Mock {
export class MockRepository<TEntity> implements IRepositoryGeneric<TEntity>{
    constructor(public ServiceLocation: string) { }
    Delete(id: number): Promise<boolean> {
        var d = defer<boolean>();
        d.resolve(null);
        return d.promise();
    }
    GetAll(): Promise<TEntity[]> {
        var d = defer<TEntity[]>();
        setTimeout(() => {
            d.resolve(null);
        }, 5000);
        return d.promise();
    }
    Get(id: number): Promise<TEntity> {
        var d = defer<TEntity>();
        d.resolve(null);
        return d.promise();
    }
    Add(entity: TEntity): Promise<TEntity> {
        var d = defer<TEntity>();
        d.resolve(null);
        return d.promise();
    }
    Update(entity: TEntity): Promise<boolean> {
        var d = defer<boolean>();
        d.resolve(false);
        return d.promise();

    }
}
}

當正確模擬此功能時,看起來jQuery ajax調用以某種方式被阻塞,這是怎么回事?

好吧,這是一個IE錯誤! WTF?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM