|
| 1 | +(function (global, factory) { |
| 2 | + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : |
| 3 | + typeof define === 'function' && define.amd ? define(factory) : |
| 4 | + (global.AsyncComputed = factory()); |
| 5 | +}(this, (function () { 'use strict'; |
| 6 | + |
| 7 | + function isComputedLazy (item) { |
| 8 | + return item.hasOwnProperty('lazy') && item.lazy |
| 9 | + } |
| 10 | + |
| 11 | + function isLazyActive (vm, key) { |
| 12 | + return vm[lazyActivePrefix + key] |
| 13 | + } |
| 14 | + |
| 15 | + const lazyActivePrefix = 'async_computed$lazy_active$', |
| 16 | + lazyDataPrefix = 'async_computed$lazy_data$'; |
| 17 | + |
| 18 | + function initLazy (data, key) { |
| 19 | + data[lazyActivePrefix + key] = false; |
| 20 | + data[lazyDataPrefix + key] = null; |
| 21 | + } |
| 22 | + |
| 23 | + function makeLazyComputed (key) { |
| 24 | + return { |
| 25 | + get () { |
| 26 | + this[lazyActivePrefix + key] = true; |
| 27 | + return this[lazyDataPrefix + key] |
| 28 | + }, |
| 29 | + set (value) { |
| 30 | + this[lazyDataPrefix + key] = value; |
| 31 | + } |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + function silentSetLazy (vm, key, value) { |
| 36 | + vm[lazyDataPrefix + key] = value; |
| 37 | + } |
| 38 | + function silentGetLazy (vm, key) { |
| 39 | + return vm[lazyDataPrefix + key] |
| 40 | + } |
| 41 | + |
| 42 | + const prefix = '_async_computed$'; |
| 43 | + const DidNotUpdate = typeof Symbol === 'function' ? Symbol('did-not-update') : {}; |
| 44 | + |
| 45 | + const AsyncComputed = { |
| 46 | + install (Vue, pluginOptions) { |
| 47 | + pluginOptions = pluginOptions || {}; |
| 48 | + |
| 49 | + Vue.config |
| 50 | + .optionMergeStrategies |
| 51 | + .asyncComputed = Vue.config.optionMergeStrategies.computed; |
| 52 | + |
| 53 | + Vue.mixin({ |
| 54 | + beforeCreate () { |
| 55 | + const optionData = this.$options.data; |
| 56 | + const asyncComputed = this.$options.asyncComputed || {}; |
| 57 | + this.$asyncComputed = {}; |
| 58 | + |
| 59 | + for (const key in this.$options.computed) { |
| 60 | + if (this.$options.computed[key].asynchronous) { |
| 61 | + asyncComputed[key] = this.$options.computed[key]; |
| 62 | + delete this.$options.computed[key]; |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + if (!Object.keys(asyncComputed).length) return |
| 67 | + |
| 68 | + this.$options.asyncComputed = asyncComputed; |
| 69 | + |
| 70 | + if (!this.$options.computed) this.$options.computed = {}; |
| 71 | + |
| 72 | + for (const key in asyncComputed) { |
| 73 | + const getter = getterFn(key, this.$options.asyncComputed[key]); |
| 74 | + this.$options.computed[prefix + key] = getter; |
| 75 | + } |
| 76 | + |
| 77 | + this.$options.data = function vueAsyncComputedInjectedDataFn () { |
| 78 | + const data = ( |
| 79 | + (typeof optionData === 'function') |
| 80 | + ? optionData.call(this) |
| 81 | + : optionData |
| 82 | + ) || {}; |
| 83 | + for (const key in asyncComputed) { |
| 84 | + const item = this.$options.asyncComputed[key]; |
| 85 | + if (isComputedLazy(item)) { |
| 86 | + initLazy(data, key); |
| 87 | + this.$options.computed[key] = makeLazyComputed(key); |
| 88 | + } else { |
| 89 | + data[key] = null; |
| 90 | + } |
| 91 | + } |
| 92 | + return data |
| 93 | + }; |
| 94 | + }, |
| 95 | + created () { |
| 96 | + for (const key in this.$options.asyncComputed || {}) { |
| 97 | + const item = this.$options.asyncComputed[key], |
| 98 | + value = generateDefault.call(this, item, pluginOptions); |
| 99 | + if (isComputedLazy(item)) { |
| 100 | + silentSetLazy(this, key, value); |
| 101 | + } else { |
| 102 | + this[key] = value; |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + for (const key in this.$options.asyncComputed || {}) { |
| 107 | + let promiseId = 0; |
| 108 | + const watcher = newPromise => { |
| 109 | + const thisPromise = ++promiseId; |
| 110 | + |
| 111 | + if (newPromise === DidNotUpdate) { |
| 112 | + return |
| 113 | + } |
| 114 | + |
| 115 | + if (!newPromise || !newPromise.then) { |
| 116 | + newPromise = Promise.resolve(newPromise); |
| 117 | + } |
| 118 | + setAsyncState(this.$asyncComputed[key], 'updating'); |
| 119 | + |
| 120 | + newPromise.then(value => { |
| 121 | + if (thisPromise !== promiseId) return |
| 122 | + setAsyncState(this.$asyncComputed[key], 'success'); |
| 123 | + this[key] = value; |
| 124 | + }).catch(err => { |
| 125 | + if (thisPromise !== promiseId) return |
| 126 | + |
| 127 | + setAsyncState(this.$asyncComputed[key], 'error'); |
| 128 | + this.$asyncComputed[key].exception = err; |
| 129 | + if (pluginOptions.errorHandler === false) return |
| 130 | + |
| 131 | + const handler = (pluginOptions.errorHandler === undefined) |
| 132 | + ? console.error.bind(console, 'Error evaluating async computed property:') |
| 133 | + : pluginOptions.errorHandler; |
| 134 | + |
| 135 | + if (pluginOptions.useRawError) { |
| 136 | + handler(err); |
| 137 | + } else { |
| 138 | + handler(err.stack); |
| 139 | + } |
| 140 | + }); |
| 141 | + }; |
| 142 | + this.$asyncComputed[key] = { |
| 143 | + exception: null, |
| 144 | + update: () => { |
| 145 | + watcher(getterOnly(this.$options.asyncComputed[key])()); |
| 146 | + } |
| 147 | + }; |
| 148 | + setAsyncState(this.$asyncComputed[key], 'updating'); |
| 149 | + this.$watch(prefix + key, watcher, { immediate: true }); |
| 150 | + } |
| 151 | + } |
| 152 | + }); |
| 153 | + } |
| 154 | + }; |
| 155 | + |
| 156 | + function setAsyncState (stateObject, state) { |
| 157 | + stateObject.state = state; |
| 158 | + stateObject.updating = state === 'updating'; |
| 159 | + stateObject.error = state === 'error'; |
| 160 | + stateObject.success = state === 'success'; |
| 161 | + } |
| 162 | + |
| 163 | + function getterOnly (fn) { |
| 164 | + if (typeof fn === 'function') return fn |
| 165 | + |
| 166 | + return fn.get |
| 167 | + } |
| 168 | + |
| 169 | + function getterFn (key, fn) { |
| 170 | + if (typeof fn === 'function') return fn |
| 171 | + |
| 172 | + let getter = fn.get; |
| 173 | + |
| 174 | + if (fn.hasOwnProperty('watch')) { |
| 175 | + const previousGetter = getter; |
| 176 | + getter = function getter () { |
| 177 | + fn.watch.call(this); |
| 178 | + return previousGetter.call(this) |
| 179 | + }; |
| 180 | + } |
| 181 | + |
| 182 | + if (fn.hasOwnProperty('shouldUpdate')) { |
| 183 | + const previousGetter = getter; |
| 184 | + getter = function getter () { |
| 185 | + if (fn.shouldUpdate.call(this)) { |
| 186 | + return previousGetter.call(this) |
| 187 | + } |
| 188 | + return DidNotUpdate |
| 189 | + }; |
| 190 | + } |
| 191 | + |
| 192 | + if (isComputedLazy(fn)) { |
| 193 | + const nonLazy = getter; |
| 194 | + getter = function lazyGetter () { |
| 195 | + if (isLazyActive(this, key)) { |
| 196 | + return nonLazy.call(this) |
| 197 | + } else { |
| 198 | + return silentGetLazy(this, key) |
| 199 | + } |
| 200 | + }; |
| 201 | + } |
| 202 | + return getter |
| 203 | + } |
| 204 | + |
| 205 | + function generateDefault (fn, pluginOptions) { |
| 206 | + let defaultValue = null; |
| 207 | + |
| 208 | + if ('default' in fn) { |
| 209 | + defaultValue = fn.default; |
| 210 | + } else if ('default' in pluginOptions) { |
| 211 | + defaultValue = pluginOptions.default; |
| 212 | + } |
| 213 | + |
| 214 | + if (typeof defaultValue === 'function') { |
| 215 | + return defaultValue.call(this) |
| 216 | + } else { |
| 217 | + return defaultValue |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + /* istanbul ignore if */ |
| 222 | + if (typeof window !== 'undefined' && window.Vue) { |
| 223 | + // Auto install in dist mode |
| 224 | + window.Vue.use(AsyncComputed); |
| 225 | + } |
| 226 | + |
| 227 | + return AsyncComputed; |
| 228 | + |
| 229 | +}))); |
0 commit comments