Adam Shaw 8 лет назад
Родитель
Сommit
b8b4ec0937

+ 43 - 44
plugins/gcal/GcalEventSource.ts

@@ -1,5 +1,5 @@
 import * as $ from 'jquery'
-import { EventSource, Promise, JsonFeedEventSource, warn, applyAll } from 'fullcalendar'
+import { EventSource, JsonFeedEventSource, warn, applyAll } from 'fullcalendar'
 
 
 export default class GcalEventSource extends EventSource {
@@ -30,61 +30,60 @@ export default class GcalEventSource extends EventSource {
   }
 
 
-  fetch(start, end, timezone) {
+  fetch(start, end, timezone, onSuccess, onFailure) {
     let url = this.buildUrl()
     let requestParams = this.buildRequestParams(start, end, timezone)
     let ajaxSettings = this.ajaxSettings || {}
-    let onSuccess = ajaxSettings.success
+    let origAjaxSuccess = ajaxSettings.success
 
     if (!requestParams) { // could have failed
-      return Promise.reject()
+      onFailure()
+      return
     }
 
     this.calendar.pushLoading()
 
-    return Promise.construct((onResolve, onReject) => {
-      $.ajax($.extend(
-        {}, // destination
-        JsonFeedEventSource.AJAX_DEFAULTS,
-        ajaxSettings,
-        {
-          url: url,
-          data: requestParams,
-          success: (responseData, status, xhr) => {
-            let rawEventDefs
-            let successRes
-
-            this.calendar.popLoading()
-
-            if (responseData.error) {
-              this.reportError('Google Calendar API: ' + responseData.error.message, responseData.error.errors)
-              onReject()
-            } else if (responseData.items) {
-              rawEventDefs = this.gcalItemsToRawEventDefs(
-                responseData.items,
-                requestParams.timeZone
-              )
-
-              successRes = applyAll(onSuccess, this, [ responseData, status, xhr ]) // passthru
-
-              if ($.isArray(successRes)) {
-                rawEventDefs = successRes
-              }
-
-              onResolve(this.parseEventDefs(rawEventDefs))
-            }
-          },
-          error: (xhr, statusText, errorThrown) => {
-            this.reportError(
-              'Google Calendar network failure: ' + statusText,
-              [ xhr, errorThrown ]
+    $.ajax($.extend(
+      {}, // destination
+      JsonFeedEventSource.AJAX_DEFAULTS,
+      ajaxSettings,
+      {
+        url: url,
+        data: requestParams,
+        success: (responseData, status, xhr) => {
+          let rawEventDefs
+          let successRes
+
+          this.calendar.popLoading()
+
+          if (responseData.error) {
+            this.reportError('Google Calendar API: ' + responseData.error.message, responseData.error.errors)
+            onFailure()
+          } else if (responseData.items) {
+            rawEventDefs = this.gcalItemsToRawEventDefs(
+              responseData.items,
+              requestParams.timeZone
             )
-            this.calendar.popLoading()
-            onReject()
+
+            successRes = applyAll(origAjaxSuccess, this, [ responseData, status, xhr ]) // passthru
+
+            if ($.isArray(successRes)) {
+              rawEventDefs = successRes
+            }
+
+            onSuccess(this.parseEventDefs(rawEventDefs))
           }
+        },
+        error: (xhr, statusText, errorThrown) => {
+          this.reportError(
+            'Google Calendar network failure: ' + statusText,
+            [ xhr, errorThrown ]
+          )
+          this.calendar.popLoading()
+          onFailure()
         }
-      ))
-    })
+      }
+    ))
   }
 
 

+ 3 - 2
src/Calendar.ts

@@ -1070,12 +1070,13 @@ export default class Calendar {
   }
 
 
-  requestEvents(start: moment.Moment, end: moment.Moment) {
+  requestEvents(start: moment.Moment, end: moment.Moment, callback) {
     return this.eventManager.requestEvents(
       start,
       end,
       this.opt('timezone'),
-      !this.opt('lazyFetching')
+      !this.opt('lazyFetching'),
+      callback
     )
   }
 

+ 7 - 6
src/View.ts

@@ -223,13 +223,14 @@ export default abstract class View extends InteractiveDateComponent {
   // -----------------------------------------------------------------------------------------------------------------
 
 
-  fetchInitialEvents(dateProfile) {
+  fetchInitialEvents(dateProfile, callback) {
     let calendar = this.calendar
     let forceAllDay = dateProfile.isRangeAllDay && !this.usesMinMaxTime
 
-    return calendar.requestEvents(
+    calendar.requestEvents(
       calendar.msToMoment(dateProfile.activeUnzonedRange.startMs, forceAllDay),
-      calendar.msToMoment(dateProfile.activeUnzonedRange.endMs, forceAllDay)
+      calendar.msToMoment(dateProfile.activeUnzonedRange.endMs, forceAllDay),
+      callback
     )
   }
 
@@ -996,9 +997,9 @@ View.watch('displayingBusinessHours', [ 'displayingDates', 'businessHourGenerato
 })
 
 
-View.watch('initialEvents', [ 'dateProfile' ], function(deps) {
-  return this.fetchInitialEvents(deps.dateProfile)
-})
+View.watch('initialEvents', [ 'dateProfile' ], function(deps, callback) {
+  this.fetchInitialEvents(deps.dateProfile, callback)
+}, null, true) // async=true
 
 
 View.watch('bindingEvents', [ 'initialEvents' ], function(deps) {

+ 4 - 7
src/common/Model.ts

@@ -157,23 +157,20 @@ export default class Model extends Class {
     }
   }
 
-  watch(name, depList, startFunc, stopFunc?) {
+  watch(name, depList, startFunc, stopFunc?, isAsync = false) {
     this.unwatch(name)
-
     this._watchers[name] = this._watchDeps(depList, (deps) => {
-      let res = startFunc.call(this, deps)
-
-      if (res && res.then) {
+      if (isAsync) { // means startFunc accepts a callback function that it will call
         this.unset(name) // put in an unset state while resolving
-        res.then((val) => {
+        startFunc.call(this, deps, (val) => {
           this.set(name, val)
         })
       } else {
+        let res = startFunc.call(this, deps)
         this.set(name, res)
       }
     }, (deps) => {
       this.unset(name)
-
       if (stopFunc) {
         stopFunc.call(this, deps)
       }

+ 0 - 66
src/common/Promise.ts

@@ -1,66 +0,0 @@
-import * as $ from 'jquery'
-
-
-const PromiseStub = {
-
-  construct: function(executor) {
-    let deferred = $.Deferred()
-    let promise = deferred.promise()
-
-    if (typeof executor === 'function') {
-      executor(
-        function(val) { // resolve
-          deferred.resolve(val)
-          attachImmediatelyResolvingThen(promise, val)
-        },
-        function() { // reject
-          deferred.reject()
-          attachImmediatelyRejectingThen(promise)
-        }
-      )
-    }
-
-    return promise
-  },
-
-  resolve: function(val) {
-    let deferred = $.Deferred().resolve(val)
-    let promise = deferred.promise()
-
-    attachImmediatelyResolvingThen(promise, val)
-
-    return promise
-  },
-
-  reject: function() {
-    let deferred = $.Deferred().reject()
-    let promise = deferred.promise()
-
-    attachImmediatelyRejectingThen(promise)
-
-    return promise
-  }
-
-}
-
-export default PromiseStub
-
-
-function attachImmediatelyResolvingThen(promise, val) {
-  promise.then = function(onResolve) {
-    if (typeof onResolve === 'function') {
-      return PromiseStub.resolve(onResolve(val))
-    }
-    return promise
-  }
-}
-
-
-function attachImmediatelyRejectingThen(promise) {
-  promise.then = function(onResolve, onReject) {
-    if (typeof onReject === 'function') {
-      onReject()
-    }
-    return promise
-  }
-}

+ 0 - 1
src/exports.ts

@@ -74,7 +74,6 @@ export { default as Class } from './common/Class'
 export { default as Mixin } from './common/Mixin'
 export { default as CoordCache } from './common/CoordCache'
 export { default as DragListener } from './common/DragListener'
-export { default as Promise } from './common/Promise'
 export { default as TaskQueue } from './common/TaskQueue'
 export { default as RenderQueue } from './common/RenderQueue'
 export { default as Scroller } from './common/Scroller'

+ 3 - 3
src/models/EventManager.ts

@@ -20,7 +20,7 @@ export default class EventManager {
   listenTo: ListenerInterface['listenTo']
   stopListeningTo: ListenerInterface['stopListeningTo']
 
-  currentPeriod: any
+  currentPeriod: EventPeriod
 
   calendar: any
   stickySource: any
@@ -34,7 +34,7 @@ export default class EventManager {
   }
 
 
-  requestEvents(start, end, timezone, force) {
+  requestEvents(start, end, timezone, force, callback) {
     if (
       force ||
       !this.currentPeriod ||
@@ -46,7 +46,7 @@ export default class EventManager {
       )
     }
 
-    return this.currentPeriod.whenReleased()
+    this.currentPeriod.whenReleased(callback)
   }
 
 

+ 4 - 7
src/models/EventPeriod.ts

@@ -1,7 +1,6 @@
 import * as moment from 'moment'
 import { removeExact, removeMatching } from '../util'
 import { isEmpty } from '../util/object'
-import Promise from '../common/Promise'
 import { default as EmitterMixin, EmitterInterface } from '../common/EmitterMixin'
 import UnzonedRange from './UnzonedRange'
 import EventInstanceGroup from './event/EventInstanceGroup'
@@ -78,7 +77,7 @@ export default class EventPeriod {
     this.requestsByUid[source.uid] = request
     this.pendingCnt += 1
 
-    source.fetch(this.start, this.end, this.timezone).then((eventDefs) => {
+    source.fetch(this.start, this.end, this.timezone, (eventDefs) => {
       if (request.status !== 'cancelled') {
         request.status = 'completed'
         request.eventDefs = eventDefs
@@ -322,13 +321,11 @@ export default class EventPeriod {
   }
 
 
-  whenReleased() {
+  whenReleased(callback) {
     if (this.releaseCnt) {
-      return Promise.resolve(this.eventInstanceGroupsById)
+      callback(this.eventInstanceGroupsById)
     } else {
-      return Promise.construct((onResolve) => {
-        this.one('release', onResolve)
-      })
+      this.one('release', callback)
     }
   }
 

+ 2 - 3
src/models/event-source/ArrayEventSource.ts

@@ -1,5 +1,4 @@
 import { removeMatching } from '../../util'
-import Promise from '../../common/Promise'
 import EventSource from './EventSource'
 import SingleEventDef from '../event/SingleEventDef'
 
@@ -41,7 +40,7 @@ export default class ArrayEventSource extends EventSource {
   }
 
 
-  fetch(start, end, timezone) {
+  fetch(start, end, timezone, onSuccess, onFailure) {
     let eventDefs = this.eventDefs
     let i
 
@@ -58,7 +57,7 @@ export default class ArrayEventSource extends EventSource {
 
     this.currentTimezone = timezone
 
-    return Promise.resolve(eventDefs)
+    onSuccess(eventDefs)
   }
 
 

+ 2 - 2
src/models/event-source/EventSource.ts

@@ -70,8 +70,8 @@ export default class EventSource extends Class {
   }
 
 
-  fetch(start, end, timezone) {
-    // subclasses must implement. must return a promise.
+  fetch(start, end, timezone, onSuccess, onFailure) {
+    // subclasses must implement. must call the `onSuccess` or `onFailure` func.
   }
 
 

+ 9 - 15
src/models/event-source/FuncEventSource.ts

@@ -1,4 +1,4 @@
-import Promise from '../../common/Promise'
+import { unpromisify } from '../../util/promise'
 import EventSource from './EventSource'
 
 
@@ -25,22 +25,16 @@ export default class FuncEventSource extends EventSource {
   }
 
 
-  fetch(start, end, timezone) {
+  fetch(start, end, timezone, onSuccess, onFailure) {
     this.calendar.pushLoading()
 
-    return Promise.construct((onResolve) => {
-      this.func.call(
-        this.calendar,
-        start.clone(),
-        end.clone(),
-        timezone,
-        (rawEventDefs) => {
-          this.calendar.popLoading()
-
-          onResolve(this.parseEventDefs(rawEventDefs))
-        }
-      )
-    })
+    unpromisify( // allow the func to return a promise
+      this.func.bind(this.calendar, start.clone(), end.clone(), timezone),
+      (rawEventDefs) => {
+        this.calendar.popLoading()
+        onSuccess(this.parseEventDefs(rawEventDefs))
+      }
+    )
   }
 
 

+ 31 - 34
src/models/event-source/JsonFeedEventSource.ts

@@ -1,7 +1,6 @@
 import * as $ from 'jquery'
 import { applyAll } from '../../util'
 import { assignTo } from '../../util/object'
-import Promise from '../../common/Promise'
 import EventSource from './EventSource'
 
 
@@ -38,10 +37,10 @@ export default class JsonFeedEventSource extends EventSource {
   }
 
 
-  fetch(start, end, timezone) {
+  fetch(start, end, timezone, onSuccess, onFailure) {
     let ajaxSettings = this.ajaxSettings
-    let onSuccess = ajaxSettings.success
-    let onError = ajaxSettings.error
+    let origAjaxSuccess = ajaxSettings.success
+    let origAjaxError = ajaxSettings.error
     let requestParams = this.buildRequestParams(start, end, timezone)
 
     // todo: eventually handle the promise's then,
@@ -50,40 +49,38 @@ export default class JsonFeedEventSource extends EventSource {
 
     this.calendar.pushLoading()
 
-    return Promise.construct((onResolve, onReject) => {
-      $.ajax(assignTo(
-        {}, // destination
-        JsonFeedEventSource.AJAX_DEFAULTS,
-        ajaxSettings,
-        {
-          url: this.url,
-          data: requestParams,
-          success: (rawEventDefs, status, xhr) => {
-            let callbackRes
-
-            this.calendar.popLoading()
-
-            if (rawEventDefs) {
-              callbackRes = applyAll(onSuccess, this, [ rawEventDefs, status, xhr ]) // redirect `this`
-
-              if (Array.isArray(callbackRes)) {
-                rawEventDefs = callbackRes
-              }
-
-              onResolve(this.parseEventDefs(rawEventDefs))
-            } else {
-              onReject()
+    $.ajax(assignTo(
+      {}, // destination
+      JsonFeedEventSource.AJAX_DEFAULTS,
+      ajaxSettings,
+      {
+        url: this.url,
+        data: requestParams,
+        success: (rawEventDefs, status, xhr) => {
+          let callbackRes
+
+          this.calendar.popLoading()
+
+          if (rawEventDefs) {
+            callbackRes = applyAll(origAjaxSuccess, this, [ rawEventDefs, status, xhr ]) // redirect `this`
+
+            if (Array.isArray(callbackRes)) {
+              rawEventDefs = callbackRes
             }
-          },
-          error: (xhr, statusText, errorThrown) => {
-            this.calendar.popLoading()
 
-            applyAll(onError, this, [ xhr, statusText, errorThrown ]) // redirect `this`
-            onReject()
+            onSuccess(this.parseEventDefs(rawEventDefs))
+          } else {
+            onFailure()
           }
+        },
+        error: (xhr, statusText, errorThrown) => {
+          this.calendar.popLoading()
+
+          applyAll(origAjaxError, this, [ xhr, statusText, errorThrown ]) // redirect `this`
+          onFailure()
         }
-      ))
-    })
+      }
+    ))
   }
 
 

+ 30 - 0
src/util/promise.ts

@@ -0,0 +1,30 @@
+
+// given a function that resolves a result asynchronously.
+// the function can either call passed-in success and failure callbacks,
+// or it can return a promise.
+// if you need to pass additional params to func, bind them first.
+export function unpromisify(func, success, failure?) {
+
+  // guard against success/failure callbacks being called more than once
+  // and guard against a promise AND callback being used together.
+  let isResolved = false
+  let wrappedSuccess = function() {
+    if (!isResolved) {
+      isResolved = true
+      success.apply(this, arguments)
+    }
+  }
+  let wrappedFailure = function() {
+    if (!isResolved) {
+      isResolved = true
+      if (failure) {
+        failure.apply(this, arguments)
+      }
+    }
+  }
+
+  let res = func(wrappedSuccess, wrappedFailure)
+  if (res && typeof res.then === 'function') {
+    res.then(wrappedSuccess, wrappedFailure)
+  }
+}

+ 6 - 10
tests/automated/util/Model.js

@@ -423,13 +423,11 @@ describe('Model', function() {
 
             var m = new Model()
             m.set('myvar', 5)
-            m.watch('myothervar', [ 'myvar' ], function(deps) {
-              var deferred = $.Deferred()
+            m.watch('myothervar', [ 'myvar' ], function(deps, callback) {
               setTimeout(function() {
-                deferred.resolve(deps.myvar * 2)
+                callback(deps.myvar * 2)
               }, 100)
-              return deferred.promise()
-            })
+            }, null, true) // async=true
 
             m.watch('myid', [ 'myvar', 'myothervar' ], funcs.generator)
             expect(spy).not.toHaveBeenCalled()
@@ -455,13 +453,11 @@ describe('Model', function() {
             var spy = spyOn(funcs, 'generator').and.callThrough()
 
             var MyClass = Model.extend()
-            MyClass.watch('myothervar', [ 'myvar' ], function(deps) {
-              var deferred = $.Deferred()
+            MyClass.watch('myothervar', [ 'myvar' ], function(deps, callback) {
               setTimeout(function() {
-                deferred.resolve(deps.myvar * 2)
+                callback(deps.myvar * 2)
               }, 100)
-              return deferred.promise()
-            })
+            }, null, true) // async=true
             MyClass.watch('myid', [ 'myvar', 'myothervar' ], funcs.generator)
 
             var m = new MyClass()

+ 0 - 34
tests/automated/util/Promise.js

@@ -1,34 +0,0 @@
-
-describe('Promise', function() {
-  var Promise = $.fullCalendar.Promise
-
-  describe('when result given at instantiation', function() {
-
-    it('executes the then function immediately', function() {
-      var p = Promise.resolve(7)
-      var executedThen = false
-
-      p.then(function(val) {
-        executedThen = true
-        expect(val).toBe(7)
-      })
-
-      expect(executedThen).toBe(true)
-    })
-
-    it('forwards on the result of the then function', function() {
-      var p = Promise.resolve(7)
-      var executedSecondThen = false
-
-      p.then(function(val) {
-        expect(val).toBe(7)
-        return 8
-      }).then(function(val) {
-        expect(val).toBe(8)
-        executedSecondThen = true
-      })
-
-      expect(executedSecondThen).toBe(true)
-    })
-  })
-})