diff --git a/src/tile.js b/src/tile.js
index 9b9604b8..263791ea 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -423,8 +423,9 @@ $.Tile.prototype = {
      * @deprecated
      */
     set context2D(value) {
-        $.console.error("[Tile.context2D] property has been deprecated. Use [Tile.setData] instead.");
+        $.console.error("[Tile.context2D] property has been deprecated. Use [Tile.setData] within dedicated update event instead.");
         this.setData(value, "context2d");
+        this.updateRenderTarget();
     },
 
     /**
@@ -473,26 +474,7 @@ $.Tile.prototype = {
         if (!this.tiledImage) {
             return $.Promise.resolve(); //async can access outside its lifetime
         }
-
-        $.console.assert("TIle.getData requires type argument! got '%s'.", type);
-
-        //we return the data synchronously immediatelly (undefined if conversion happens)
-        const cache = this.getCache(this._wcKey);
-        if (!cache) {
-            const targetCopyKey = this.__restore ? this.originalCacheKey : this.cacheKey;
-            const origCache = this.getCache(targetCopyKey);
-            if (!origCache) {
-                $.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
-            }
-
-            //todo consider calling addCache with callback, which can avoid creating data item only to just discard it
-            //  in case we addCache with existing key and the current tile just gets attached as a reference
-            //  .. or explicitly check that such cache does not exist globally (now checking only locally)
-            return origCache.getDataAs(type, true).then(data => {
-                return this.addCache(this._wcKey, data, type, false, false).await();
-            });
-        }
-        return cache.getDataAs(type, false);
+        return this._getOrCreateWorkingCacheData(type);
     },
 
     /**
@@ -521,10 +503,10 @@ $.Tile.prototype = {
             return null; //async context can access the tile outside its lifetime
         }
 
-        const cache = this.getCache(this._wcKey);
+        let cache = this.getCache(this._wcKey);
         if (!cache) {
-            $.console.error("[Tile::setData] You cannot set data without calling tile.getData()! The working cache is not initialized!");
-            return $.Promise.resolve();
+            this._getOrCreateWorkingCacheData(undefined);
+            cache = this.getCache(this._wcKey);
         }
         return cache.setDataAs(value, type);
     },
@@ -611,8 +593,11 @@ $.Tile.prototype = {
      * @param {string} key cache key, must be unique (we recommend re-using this.cacheTile
      *   value and extend it with some another unique content, by default overrides the existing
      *   main cache used for drawing, if not existing.
-     * @param {*} data data to cache - this data will be IGNORED if cache already exists!
-     * @param {string} [type=undefined] data type, will be guessed if not provided
+     * @param {*} data this data will be IGNORED if cache already exists; therefore if
+     *   `typeof data === 'function'` holds (both async and normal functions), the data is called to obtain
+     *   the data item: this is an optimization to load data only when necessary.
+     * @param {string} [type=undefined] data type, will be guessed if not provided (not recommended),
+     *   if data is a callback the type is a mandatory field, not setting it results in undefined behaviour
      * @param {boolean} [setAsMain=false] if true, the key will be set as the tile.cacheKey
      * @param [_safely=true] private
      * @returns {OpenSeadragon.CacheRecord|null} - The cache record the tile was attached to.
@@ -628,6 +613,9 @@ $.Tile.prototype = {
                     "Automated deduction is potentially unsafe: prefer specification of data type explicitly.");
                 this.__typeWarningReported = true;
             }
+            if (typeof data === 'function') {
+                $.console.error("[TileCache.cacheTile] options.data as a callback requires type argument! Current is " + type);
+            }
             type = $.convertor.guessType(data);
         }
 
@@ -677,6 +665,30 @@ $.Tile.prototype = {
         // as drawers request data for drawing
     },
 
+    /**
+     * Initializes working cache if it does not exist.
+     * @param {string|undefined} type initial cache type to create
+     * @return {OpenSeadragon.Promise<?>} data-awaiting promise with the cache data
+     * @private
+     */
+    _getOrCreateWorkingCacheData: function (type) {
+        const cache = this.getCache(this._wcKey);
+        if (!cache) {
+            const targetCopyKey = this.__restore ? this.originalCacheKey : this.cacheKey;
+            const origCache = this.getCache(targetCopyKey);
+            if (!origCache) {
+                $.console.error("[Tile::getData] There is no cache available for tile with key %s", targetCopyKey);
+            }
+            // Here ensure type is defined, rquired by data callbacks
+            type = type || origCache.type;
+
+            // Here we use extensively ability to call addCache with callback: working cache is created only if not
+            // already in memory (=> shared).
+            return this.addCache(this._wcKey, () => origCache.getDataAs(type, true), type, false, false).await();
+        }
+        return cache.getDataAs(type, false);
+    },
+
     /**
      * Get the number of caches available to this tile
      * @returns {number} number of caches
diff --git a/src/tilecache.js b/src/tilecache.js
index f6667bb9..3a941170 100644
--- a/src/tilecache.js
+++ b/src/tilecache.js
@@ -75,7 +75,7 @@
 
         /**
          * Await ongoing process so that we get cache ready on callback.
-         * @returns {Promise<any>}
+         * @returns {OpenSeadragon.Promise<?>}
          */
         await() {
             if (!this._promise) { //if not cache loaded, do not fail
@@ -403,9 +403,24 @@
 
             // first come first served, data for existing tiles is NOT overridden
             if (this._tiles.length < 1) {
+                // Since we IGNORE new data if already initialized, we support 'data getter'
+                if (typeof data === 'function') {
+                    data = data();
+                }
+
+                // If we receive async callback, we consume the async state
+                if (data instanceof $.Promise) {
+                    this._promise = data.then(d => {
+                        this._data = d;
+                        return d;
+                    });
+                    this._data = null;
+                } else {
+                    this._promise = $.Promise.resolve(data);
+                    this._data = data;
+                }
+
                 this._type = type;
-                this._promise = $.Promise.resolve(data);
-                this._data = data;
                 this.loaded = true;
                 this._tiles.push(tile);
             } else if (!this._tiles.includes(tile)) {
@@ -734,7 +749,8 @@
          * @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
          *   Used if options.cacheKey not set.
          * @param {Image} options.image - The image of the tile to cache. Deprecated.
-         * @param {*} options.data - The data of the tile to cache.
+         * @param {*} options.data - The data of the tile to cache. If `typeof data === 'function'` holds,
+         *   the data is called to obtain the data item: this is an optimization to load data only when necessary.
          * @param {string} [options.dataType] - The data type of the tile to cache. Required.
          * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
          *   function will release an old tile. The cutoff option specifies a tile level at or below which
@@ -777,6 +793,12 @@
             if (!options.dataType) {
                 $.console.error("[TileCache.cacheTile] options.dataType is newly required. " +
                     "For easier use of the cache system, use the tile instance API.");
+
+                // We need to force data acquisition now to guess the type
+                if (typeof options.data === 'function') {
+                    $.console.error("[TileCache.cacheTile] options.dataType is mandatory " +
+                        " when data item is a callback!");
+                }
                 options.dataType = $.convertor.guessType(options.data);
             }