Skip to content

Commit

Permalink
Merge 9d4eac3 into 394a4ae
Browse files Browse the repository at this point in the history
  • Loading branch information
NaridaL committed May 24, 2020
2 parents 394a4ae + 9d4eac3 commit a95a578
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 116 deletions.
6 changes: 3 additions & 3 deletions src/ObservableGroupMap.ts
Expand Up @@ -147,7 +147,7 @@ export class ObservableGroupMap<G, T> extends ObservableMap<G, IObservableArray<
this._disposeBaseObserver()
for (let i = 0; i < this._base.length; i++) {
const item = this._base[i]
const grouperItemInfo: GroupItemInfo = item[this._ogmInfoKey]
const grouperItemInfo: GroupItemInfo = item[this._ogmInfoKey]!
grouperItemInfo.reaction()

delete item[this._ogmInfoKey]
Expand All @@ -172,7 +172,7 @@ export class ObservableGroupMap<G, T> extends ObservableMap<G, IObservableArray<
arr.length--
} else {
arr[itemIndex] = arr[arr.length - 1]
arr[itemIndex][this._ogmInfoKey].groupArrIndex = itemIndex
arr[itemIndex][this._ogmInfoKey]!.groupArrIndex = itemIndex
arr.length--
}
}
Expand Down Expand Up @@ -210,7 +210,7 @@ export class ObservableGroupMap<G, T> extends ObservableMap<G, IObservableArray<
}

private _removeItem(item: GroupItem) {
const grouperItemInfo = item[this._ogmInfoKey]
const grouperItemInfo = item[this._ogmInfoKey]!
this._removeFromGroupArr(grouperItemInfo.groupByValue, grouperItemInfo.groupArrIndex)
grouperItemInfo.reaction()

Expand Down
2 changes: 1 addition & 1 deletion src/action-async.ts
Expand Up @@ -53,7 +53,7 @@ let inOrderExecution: () => Promise<void>

const actionAsyncContextStack: IActionAsyncContext[] = []

export async function task<R>(value: R | PromiseLike<R>): Promise<R> {
export async function task<R>(this: any, value: R | PromiseLike<R>): Promise<R> {
const ctx = actionAsyncContextStack[actionAsyncContextStack.length - 1]

if (!ctx) {
Expand Down
39 changes: 19 additions & 20 deletions src/computedFn.ts
Expand Up @@ -9,21 +9,21 @@ import {
} from "mobx"

/**
* computedFn takes a function with an arbitrarily amount of arguments,
* and memoized the output of the function based on the arguments passed in.
*
* computedFn takes a function with an arbitrary amount of arguments,
* and memoizes the output of the function based on the arguments passed in.
*
* computedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments
* that is accepted. However, the amount of arguments must be consistent and default arguments are not supported.
*
* By default the output of a function call will only be memoized as long as the
* output is being observed.
*
* The function passes into `computedFn` should be pure, not be an action and only be relying on
* that is accepted. However, the amount of arguments must be constant and default arguments are not supported.
*
* By default the output of a function call will only be memoized as long as the
* output is being observed.
*
* The function passes into `computedFn` should be pure, not be an action and only be relying on
* observables.
*
*
* Setting `keepAlive` to `true` will cause the output to be forcefully cached forever.
* Note that this might introduce memory leaks!
*
*
* @example
* const store = observable({
a: 1,
Expand All @@ -36,16 +36,16 @@ import {
const d = autorun(() => {
// store.m(3) will be cached as long as this autorun is running
console.log((store.m(3) * store.c))
console.log(store.m(3) * store.c)
})
*
* @param fn
*
* @param fn
* @param keepAliveOrOptions
*/
export function computedFn<T extends (...args: any[]) => any>(
fn: T,
keepAliveOrOptions: IComputedValueOptions<ReturnType<T>> | boolean = false
) {
): T {
if (isAction(fn)) throw new Error("computedFn shouldn't be used on actions")

let memoWarned = false
Expand All @@ -56,8 +56,7 @@ export function computedFn<T extends (...args: any[]) => any>(
: keepAliveOrOptions
const d = new DeepMap<IComputedValue<any>>()

return function (...args: Parameters<T>): ReturnType<T> {
const self = this
return function (this: any, ...args: any[]): any {
const entry = d.entry(args)
// cache hit, return
if (entry.exists()) return entry.get().get()
Expand All @@ -69,12 +68,12 @@ export function computedFn<T extends (...args: any[]) => any>(
)
memoWarned = true
}
return fn.apply(self, args)
return fn.apply(this, args)
}
// create new entry
const c = computed(
() => {
return fn.apply(self, args)
return fn.apply(this, args)
},
{
...opts,
Expand All @@ -89,5 +88,5 @@ export function computedFn<T extends (...args: any[]) => any>(
})
// return current val
return c.get()
}
} as any
}
4 changes: 2 additions & 2 deletions src/create-transformer.ts
Expand Up @@ -42,9 +42,9 @@ export function createTransformer<A, B>(

// Memoizes: object id -> reactive view that applies transformer to the object
let views: { [id: number]: IComputedValue<B> } = {}
let onCleanup: Function = undefined
let onCleanup: Function | undefined = undefined
let keepAlive: boolean = false
let debugNameGenerator: Function = undefined
let debugNameGenerator: Function | undefined = undefined
if (typeof arg2 === "object") {
onCleanup = arg2.onCleanup
keepAlive = arg2.keepAlive !== undefined ? arg2.keepAlive : false
Expand Down
4 changes: 2 additions & 2 deletions src/create-view-model.ts
Expand Up @@ -65,7 +65,7 @@ export class ViewModel<T> implements IViewModel<T> {
...additionalDescriptor,
configurable: true,
get: () => {
if (isComputedProp(model, key)) return this.localComputedValues.get(key).get()
if (isComputedProp(model, key)) return this.localComputedValues.get(key)!.get()
if (this.isPropertyDirty(key)) return this.localValues.get(key)
else return this.model[key as keyof T]
},
Expand All @@ -87,7 +87,7 @@ export class ViewModel<T> implements IViewModel<T> {
@action.bound
submit() {
keys(this.localValues).forEach((key: keyof T) => {
const source = this.localValues.get(key)
const source = this.localValues.get(key)!
const destination = this.model[key]
if (isObservableArray(destination)) {
destination.replace(source as any)
Expand Down
2 changes: 1 addition & 1 deletion src/deepMap.ts
Expand Up @@ -75,7 +75,7 @@ export class DeepMapEntry<T> {
export class DeepMap<T> {
private store = new Map<any, any>()
private argsLength = -1
private last: DeepMapEntry<T>
private last: DeepMapEntry<T> | undefined

entry(args: any[]): DeepMapEntry<T> {
if (this.argsLength === -1) this.argsLength = args.length
Expand Down
21 changes: 10 additions & 11 deletions src/deepObserve.ts
Expand Up @@ -17,10 +17,11 @@ type IChange = IObjectDidChange | IArrayChange | IArraySplice | IMapDidChange
type Entry = {
dispose: IDisposer
path: string
parent?: Entry
parent: Entry | undefined
}

function buildPath(entry: Entry): string {
function buildPath(entry: Entry | undefined): string {
if (!entry) return "ROOT"
const res: string[] = []
while (entry.parent) {
res.push(entry.path)
Expand Down Expand Up @@ -58,7 +59,7 @@ export function deepObserve<T = any>(
const entrySet = new WeakMap<any, Entry>()

function genericListener(change: IChange) {
const entry = entrySet.get(change.object)
const entry = entrySet.get(change.object)!
processChange(change, entry)
listener(change, buildPath(entry), target)
}
Expand Down Expand Up @@ -98,20 +99,18 @@ export function deepObserve<T = any>(
}
}

function observeRecursively(thing: any, parent: Entry, path: string) {
function observeRecursively(thing: any, parent: Entry | undefined, path: string) {
if (isRecursivelyObservable(thing)) {
if (entrySet.has(thing)) {
const entry = entrySet.get(thing)
const entry = entrySet.get(thing)
if (entry) {
if (entry.parent !== parent || entry.path !== path)
// MWE: this constraint is artificial, and this tool could be made to work with cycles,
// but it increases administration complexity, has tricky edge cases and the meaning of 'path'
// would become less clear. So doesn't seem to be needed for now
throw new Error(
`The same observable object cannot appear twice in the same tree, trying to assign it to '${buildPath(
parent
)}/${path}', but it already exists at '${buildPath(entry.parent)}/${
entry.path
}'`
`The same observable object cannot appear twice in the same tree,` +
` trying to assign it to '${buildPath(parent)}/${path}',` +
` but it already exists at '${buildPath(entry.parent)}/${entry.path}'`
)
} else {
const entry = {
Expand Down
151 changes: 85 additions & 66 deletions src/from-promise.ts
Expand Up @@ -13,10 +13,10 @@ type CaseHandlers<U, T> = {
rejected?: (e: any) => U
}

export type IBasePromiseBasedObservable<T> = {
export interface IBasePromiseBasedObservable<T> extends PromiseLike<T> {
isPromiseBasedObservable: true
case<U>(handlers: CaseHandlers<U, T>, defaultFulfilled?: boolean): U
} & PromiseLike<T>
}

export type IPendingPromise = {
readonly state: "pending"
Expand All @@ -36,7 +36,10 @@ export type IRejectedPromise = {
export type IPromiseBasedObservable<T> = IBasePromiseBasedObservable<T> &
(IPendingPromise | IFulfilledPromise<T> | IRejectedPromise)

function caseImpl<U, T>(handlers: CaseHandlers<U, T>): U {
function caseImpl<U, T>(
this: IPromiseBasedObservable<T>,
handlers: CaseHandlers<U, T>
): U | T | undefined {
switch (this.state) {
case PENDING:
return handlers.pending && handlers.pending(this.value)
Expand All @@ -47,50 +50,6 @@ function caseImpl<U, T>(handlers: CaseHandlers<U, T>): U {
}
}

function createObservablePromise(origPromise: any, oldPromise?: any) {
invariant(arguments.length <= 2, "fromPromise expects up to two arguments")
invariant(
typeof origPromise === "function" ||
(typeof origPromise === "object" &&
origPromise &&
typeof origPromise.then === "function"),
"Please pass a promise or function to fromPromise"
)
if (origPromise.isPromiseBasedObservable === true) return origPromise

if (typeof origPromise === "function") {
// If it is a (reject, resolve function, wrap it)
origPromise = new Promise(origPromise as any)
}

const promise = origPromise as any
origPromise.then(
action("observableFromPromise-resolve", (value: any) => {
promise.value = value
promise.state = FULFILLED
}),
action("observableFromPromise-reject", (reason: any) => {
promise.value = reason
promise.state = REJECTED
})
)

promise.isPromiseBasedObservable = true
promise.case = caseImpl
const oldData = oldPromise && oldPromise.state === FULFILLED ? oldPromise.value : undefined
extendObservable(
promise,
{
value: oldData,
state: PENDING,
},
{},
{ deep: false }
)

return promise
}

/**
* `fromPromise` takes a Promise, extends it with 2 observable properties that track
* the status of the promise and returns it. The returned object has the following observable properties:
Expand Down Expand Up @@ -189,25 +148,85 @@ function createObservablePromise(origPromise: any, oldPromise?: any) {
* @param {IThenable<T>} oldPromise? The previously observed promise
* @returns {IPromiseBasedObservable<T>}
*/
export const fromPromise: {
<T>(promise: PromiseLike<T>, oldPromise?: PromiseLike<T>): IPromiseBasedObservable<T>
reject<T>(reason: any): IRejectedPromise & IBasePromiseBasedObservable<T>
resolve<T>(value?: T): IFulfilledPromise<T> & IBasePromiseBasedObservable<T>
} = createObservablePromise as any

fromPromise.reject = action("fromPromise.reject", function (reason: any) {
const p: any = fromPromise(Promise.reject(reason))
p.state = REJECTED
p.value = reason
return p
}) as any

fromPromise.resolve = action("fromPromise.resolve", function (value: any = undefined) {
const p: any = fromPromise(Promise.resolve(value))
p.state = FULFILLED
p.value = value
return p
}) as any
export function fromPromise<T>(
origPromise: PromiseLike<T>,
oldPromise?: PromiseLike<T>
): IPromiseBasedObservable<T> {
invariant(arguments.length <= 2, "fromPromise expects up to two arguments")
invariant(
typeof origPromise === "function" ||
(typeof origPromise === "object" &&
origPromise &&
typeof origPromise.then === "function"),
"Please pass a promise or function to fromPromise"
)
if ((origPromise as any).isPromiseBasedObservable === true) return origPromise as any

if (typeof origPromise === "function") {
// If it is a (reject, resolve function, wrap it)
origPromise = new Promise(origPromise as any)
}

const promise = origPromise as any
origPromise.then(
action("observableFromPromise-resolve", (value: any) => {
promise.value = value
promise.state = FULFILLED
}),
action("observableFromPromise-reject", (reason: any) => {
promise.value = reason
promise.state = REJECTED
})
)

promise.isPromiseBasedObservable = true
promise.case = caseImpl
const oldData =
oldPromise && (oldPromise as any).state === FULFILLED
? (oldPromise as any).value
: undefined
extendObservable(
promise,
{
value: oldData,
state: PENDING,
},
{},
{ deep: false }
)

return promise
}
// export const fromPromise: {
// <T>(promise: PromiseLike<T>, oldPromise?: PromiseLike<T>): IPromiseBasedObservable<T>
// reject<T>(reason: any): IRejectedPromise & IBasePromiseBasedObservable<T>
// resolve<T>(value?: T): IFulfilledPromise<T> & IBasePromiseBasedObservable<T>
// } = createObservablePromise as any
export namespace fromPromise {
export const reject = action("fromPromise.reject", function <T>(
reason: any
): IRejectedPromise & IBasePromiseBasedObservable<T> {
const p: any = fromPromise(Promise.reject(reason))
p.state = REJECTED
p.value = reason
return p
})

function resolveBase<T>(
value?: T
): IFulfilledPromise<T | undefined> & IBasePromiseBasedObservable<T>
function resolveBase<T>(value: T): IFulfilledPromise<T> & IBasePromiseBasedObservable<T>
function resolveBase<T>(
value: T | undefined = undefined
): IFulfilledPromise<T | undefined> & IBasePromiseBasedObservable<T | undefined> {
const p: any = fromPromise(Promise.resolve(value))
p.state = FULFILLED
p.value = value
return p
}

export const resolve = action("fromPromise.resolve", resolveBase)
}

/**
* Returns true if the provided value is a promise-based observable.
Expand Down

0 comments on commit a95a578

Please sign in to comment.