summaryrefslogtreecommitdiff
path: root/calendar/providers/gdata/modules/shim/Task.jsm
blob: 947007a18896887841103ba7947c3c0d7c6e4424 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [
  "Task"
];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;

Cu.import("resource://gdata-provider/modules/shim/Promise.jsm");

// The following error types are considered programmer errors, which should be
// reported (possibly redundantly) so as to let programmers fix their code.
var ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError", "TypeError"];

var gCurrentTask = null;

function linesOf(string) {
  let reLine = /([^\r\n])+/g;
  let match;
  while ((match = reLine.exec(string))) {
    yield [match[0], match.index];
  }
};

function isGenerator(aValue) {
  return Object.prototype.toString.call(aValue) == "[object Generator]";
}

this.Task = {
  spawn: function Task_spawn(aTask) {
    return createAsyncFunction(aTask).call(undefined);
  },

  async: function Task_async(aTask) {
    if (typeof(aTask) != "function") {
      throw new TypeError("aTask argument must be a function");
    }

    return createAsyncFunction(aTask);
  },

  Result: function Task_Result(aValue) {
    this.value = aValue;
  }
};

function createAsyncFunction(aTask) {
  let asyncFunction = function () {
    let result = aTask;
    if (aTask && typeof(aTask) == "function") {
      if (aTask.isAsyncFunction) {
        throw new TypeError(
          "Cannot use an async function in place of a promise. " +
          "You should either invoke the async function first " +
          "or use 'Task.spawn' instead of 'Task.async' to start " +
          "the Task and return its promise.");
      }

      try {
        // Let's call into the function ourselves.
        result = aTask.apply(this, arguments);
      } catch (ex) {
        if (ex instanceof Task.Result) {
          return Promise.resolve(ex.value);
        } else {
          return Promise.reject(ex);
        }
      }
    }

    if (isGenerator(result)) {
      // This is an iterator resulting from calling a generator function.
      return new TaskImpl(result).deferred.promise;
    }

    // Just propagate the given value to the caller as a resolved promise.
    return Promise.resolve(result);
  };

  asyncFunction.isAsyncFunction = true;

  return asyncFunction;
}

function TaskImpl(iterator) {
  this.deferred = Promise.defer();
  this._iterator = iterator;
  this._run(true);
}

TaskImpl.prototype = {
  deferred: null,
  _iterator: null,

  _run: function TaskImpl_run(aSendResolved, aSendValue) {
    try {
      gCurrentTask = this;

      try {
        let yielded = aSendResolved ? this._iterator.send(aSendValue)
                                    : this._iterator.throw(aSendValue);
        this._handleResultValue(yielded);
      } catch (ex) {
        if (ex instanceof Task.Result) {
          this.deferred.resolve(ex.value);
        } else if (ex instanceof StopIteration) {
          this.deferred.resolve(undefined);
        } else {
          this._handleException(ex);
        }
      }
    } finally {
      if (gCurrentTask == this) {
        gCurrentTask = null;
      }
    }
  },

  _handleResultValue: function TaskImpl_handleResultValue(aValue) {
    if (isGenerator(aValue)) {
      aValue = Task.spawn(aValue);
    }

    if (aValue && typeof(aValue.then) == "function") {
      aValue.then(this._run.bind(this, true),
                  this._run.bind(this, false));
    } else {
      this._run(true, aValue);
    }
  },

  _handleException: function TaskImpl_handleException(aException) {
    gCurrentTask = this;
    this.deferred.reject(aException);
  }
};