import { isObject } from '@goldfishjs/utils' ; import { call , getCurrent , IErrorCallback , ChangeOptions } from './dep' ; import { isArray } from './utils' ; import isRaw from './isRaw' ; export type Unwatch = ( ) => void ; export type IWatchCallback < N , O = any > = ( newValue : N , oldValue ? : O , options ? : ChangeOptions ) => void ; export type IWatchExpressionFn < R > = ( ) => R ; export interface IWatchOptions { deep ? : boolean ; immediate ? : boolean ; onError ? : IErrorCallback ; } class Watcher < R > { private isStopped = false ; private cb : IWatchCallback < R > ; private options : IWatchOptions ; private fn : IWatchExpressionFn < R > ; private isFirstTime = true ; private removeListeners : Function [ ] = [ ] ; constructor ( fn , cb , options ) { this . fn = fn ; this . cb = cb ; this . options = options ; this . watch ( ) ; } public stop ( ) { this . isStopped = true ; this . callRemoveListeners ( ) ; } private callRemoveListeners ( ) { this . removeListeners . forEach ( r => r ( ) ) ; this . removeListeners . splice ( 0 , this . removeListeners . length ) ; } private deepVisit ( obj ) { if ( obj && isRaw ( obj ) ) { return ; } if ( isObject ( obj ) ) { for ( const key in obj ) { if ( ! Object . prototype . hasOwnProperty . call ( obj , key ) ) { continue ; } this . deepVisit ( obj [ key ] ) ; } } else if ( isArray ( obj ) ) { for ( let i = 0 , il = obj . length ; i < il ; i += 1 ) { this . deepVisit ( obj [ i ] ) ; } } } private watch ( ) { let result ; call ( ( ) => { result = this . fn ( ) ; if ( this . options . deep ) { this . deepVisit ( result ) ; } this . removeListeners . push ( ... getCurrent ( ) . addChangeListener ( ( n , o , options ) => { this . callRemoveListeners ( ) ; if ( this . isStopped ) { return ; } const currentResult = this . watch ( ) ; if ( this . options . deep || options . type === 'notify' || currentResult !== result ) { try { this . cb ( currentResult , result , options ) ; } catch ( e ) { this . options . onError && this . options . onError ( e ) ; } } } ) ) ; } , this . options . onError ) ; if ( this . isFirstTime && this . options . immediate ) { this . isFirstTime = false ; Promise . resolve ( ) . then ( ( ) => { this . cb ( result ) ; } ) . catch ( e => { this . options . onError && this . options . onError ( e ) ; } ) ; } return result ; } } export default function watch < R > ( fn , cb , options = { deep : false , immediate : false } ) { const watcher = new Watcher ( fn , cb , options ) ; return ( ) => watcher . stop ( ) ; }