CancellationToken

Abort async function in ES2016

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.29/browser-polyfill.min.js"></script>
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<div>
  <button data-bind="click: start">Start</button>
  <button data-bind="click: cancel">Cancel</button>
</div>

<i class="fa fa-square-o fa-spin fa-2x"
   data-bind="visible: running"></i>
   
<ul data-bind="foreach: items">
  <li data-bind="text: $data"></li>
</ul>

<p data-bind="text: result"></p>

Babel + JSX

class ViewModel {
	duration = ko.observable(5000);
  items = ko.observableArray([]);
  running = ko.observable(false);
  result = ko.observable("");
  cancellationToken = null;
  
  // start ボタンクリックで呼び出されるメソッド
  async start() {
  	this.items([]);			// 初期化
    this.result("");		// 初期化
    this.running(true);	// 実行中にする(アイコンが回転)
    // トークンを作成してフィールドにセット
  	this.cancellationToken = new CancellationToken();
    try {
    	for (var i = 0; i < 5; i++) {	// 5回まで
      const before = Date.now()
      	await delay(1000, this.cancellationToken); // トークンを渡して呼び出しconst after = Date.now()
        this.items.push("running " + (i + 1));		 // running n を追加
      }
      this.result("DONE!");		// キャンセルされなかったらこれが呼び出される
    } catch (e) {
    	// キャンセルされるとここに飛ぶ
    	this.result(e.cancelled ? 'CANCELLED!' : e.message);
    }
    this.cancellationToken = null; // トークンを除去
    this.running(false);	// 実行中を解除(アイコンが消える)
  }
  
  // cancel ボタンクリックで呼び出されるメソッド
  async cancel() {
  	// トークンがフィールドにあればキャンセルを呼び出す
  	if (this.cancellationToken) {
    	this.cancellationToken.cancel();
    }
  }
}

function delay(duration, cancellationToken = null) {
	return new Promise((resolve, reject) => {
  	setTimeout(resolve, duration);
    if (cancellationToken) {
      cancellationToken.register(reject);
    }
  });
}

/**
 * cancellation token to abort async function
 * @see http://stackoverflow.com/questions/32897385/abort-ecmascript7-async-function
 */
class CancellationToken {

  isCancellationRequested = false;

  constructor(parentToken = null) {
    this.cancellationPromise = new Promise(resolve => {
      this.cancel = e => {
        this.isCancellationRequested = true;
        if (e) {
          resolve(e);
        } else {
          var err = new Error("cancelled");
          err.cancelled = true;
          resolve(err);
        }
      }
    });
    if (parentToken && parentToken instanceof CancellationToken) {
      parentToken.register(this.cancel);
    }
  }

  register(callback) {
   ...