Прогресс и отмена в асинхронных процессах

Дегтярев Евгений

Дерево задач 

Прогресс в JQuery

function doSomething() {   
    var dfd = $.Deferred();

    var count = 0;
    var intervalId = setInterval(function() {
        dfd.notify(count++);
        count > 3 && clearInterval(intervalId);
    }, 500);

    return dfd.promise();
};

var promise = doSomething();

promise.progress(function(prog) {
  console.log(prog);
});​

Прогресс в Q

function requestOkText(url) {
    return Q.Promise(function(resolve, reject, notify) {
        var r = new XMLHttpRequest();
        r.open("GET", url, true);
        r.onload = ()=>resolve(r.responseText);;
        r.onerror = ()=>reject(new Error("Can't XHR "));
        r.onprogress = ()=>notify(event.loaded / event.total);
        r.send();
    });
}

return requestOkText("google.com").progress(progress => {
    // We get notified of the upload's progress
});

Не пользуйтесь таким прогрессом

  • Deprecated в библиотеках
  • Сложно делать композицию
  • Нет реализаций для Promise.all и Promise.map
  • Много лишнего кода
  • Несовместимо со стандартом Promises/A+
  • Лок на конкретной реализации Promise

 

Правильный путь

function returnsPromiseWithProgress(progressHandler) {
    return doFirstAction().then(function() {
        progressHandler(0.33);
    }).then(doSecondAction).then(function() {
        progressHandler(0.66);
    }).then(doThirdAction).then(function() {
        progressHandler(1.00);
    });
}

returnsPromiseWithProgress(function(progress) {
    ui.progressbar.setWidth((progress * 200) + "px");
});

Использовать C# подход 

Идеальный вариант

function returnsPromiseWithProgress(progress: IProgress) {
    const firstActionProgress = progress.child();
    const secondActionProgress = progress.child();
    const thirdActionProgress = progress.child();
    
    return doFirstAction(firstActionProgress)
        .then(() => doSecondAction(secondActionProgress))
        .then(() => doThirdAction(thirdActionProgress));
}

const progress = new Progress();
setInterval(function() {
    ui.progressbar.set(progress.current());
}, 100);

returnsPromiseWithProgress(progress);
export interface IProgress {
  current(): number;
  add(value: number): void;
  set(value: number): void;
  complete(): void;
  eliminate(): void;
  child(weight?: number): IProgress;
}

Отмена

Отменить promise нельзя

Но если очень хочется то можно

Вариант 1. bluebird

const promise = doSomeWorkAsync();
cancelButton.onclick = function() {
    promise.cancel();
};

bluebird 2

function makeCancellableRequest(url) {
    var xhr = new XMLHttpRequest;
    return new Promise(function (resolve, reject) {
        xhr.addEventListener("error", reject);
        xhr.addEventListener("load", resolve);
        xhr.open("GET", url);
        xhr.send(null);
    })
    .cancellable()
    .catch(Promise.CancellationError, e => {
        xhr.abort();
        throw e;
    });
}

bluebird 3

function makeCancellableRequest(url) {
    return new Promise((resolve, reject, onCancel)=>{
        var xhr = new XMLHttpRequest();
        ...
        onCancel(function() {
            xhr.abort();
        });
    });
}

Особенности

const promiseRoot = doSomeWork1();
const promiseChild1 = promiseRoot.then(doSomeWork2);
const promiseChild2 = promiseRoot.then(doSomeWork3);
promiseChild1.cancel();

Вариант 2. Rx

const subscription = new Rx.Subscription();
const promise = new Promise(resolve => {
  const id = setTimeout(resolve, 1000);
  subscription.add(() => clearTimeout(id));
});
promise.then(() => console.log('done'));
subcscription.unsubscribe();

Вариант 3.

Другой promsie

CancellationToken как в C#

https://github.com/tc39/proposal-cancelable-promises

 

 

CancellationToken

function createToken() {
  const token = {};
   token.promise = new Promise(resolve => {
    cancel = (reason) => {
      token.reason = reason;
      resolve(reason);
    });
  };
  return { token, cancel };
}
const { token, cancel } = createToken();
function delay(ms, token) {
  return new Promise(resolve => {
    const id = setTimeout(resolve, ms);
    token.promise.then((reason) => clearTimeout(id));
  });
};

CancellationToken

Made with Slides.com