1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        手寫一個基于 Proxy 的緩存庫

        共 10865字,需瀏覽 22分鐘

         ·

        2021-02-19 13:04

        作者:jump jump
        來源:SegmentFault 思否社區(qū)




        Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。




        項目演進


        任何項目都不是一觸而就的,下面是關于 Proxy 緩存庫的編寫思路。希望能對大家有一些幫助。


        proxy handler 添加緩存


        當然,其實代理器中的 handler 參數(shù)也是一個對象,那么既然是對象,當然可以添加數(shù)據(jù)項,如此,我們便可以基于 Map 緩存編寫 memoize 函數(shù)用來提升算法遞歸性能。


        type?TargetFun?=?(...args:?any[])?=>?V

        function?memoize(fn:?TargetFun)?{
        ??return?new?Proxy(fn,?{
        ????//?此處目前只能略過?或者?添加一個中間層集成 Proxy 和?對象。
        ????//?在對象中添加?cache
        ????//?@ts-ignore
        ????cache:?new?Map(),
        ????apply(target,?thisArg,?argsList)?{
        ??????//?獲取當前的?cache
        ??????const?currentCache?=?(this?as?any).cache
        ??????
        ??????//?根據(jù)數(shù)據(jù)參數(shù)直接生成?Map?的?key
        ??????let?cacheKey?=?argsList.toString();
        ??????
        ??????//?當前沒有被緩存,執(zhí)行調(diào)用,添加緩存
        ??????if?(!currentCache.has(cacheKey))?{
        ????????currentCache.set(cacheKey,?target.apply(thisArg,?argsList));
        ??????}
        ??????
        ??????//?返回被緩存的數(shù)據(jù)
        ??????return?currentCache.get(cacheKey);
        ????}
        ??});
        }


        我們可以嘗試 memoize fibonacci 函數(shù),經(jīng)過了代理器的函數(shù)有非常大的性能提升(肉眼可見):


        const?fibonacci?=?(n:?number):?number?=>?(n?<=?1???1?:?fibonacci(n?-?1)?+?fibonacci(n?-?2));
        const?memoizedFibonacci?=?memoize(fibonacci);

        for?(let?i?=?0;?i?for?(let?i?=?0;?i?


        自定義函數(shù)參數(shù)


        我們?nèi)耘f可以利用之前博客介紹的的函數(shù)生成唯一值,只不過我們不再需要函數(shù)名了:


        const?generateKeyError?=?new?Error("Can't?generate?key?from?function?argument")

        //?基于函數(shù)參數(shù)生成唯一值
        export?default?function?generateKey(argument:?any[]):?string?{
        ??try{
        ????return?`${Array.from(argument).join(',')}`
        ??}catch(_)?{
        ????throw?generateKeyError
        ??}
        }


        雖然庫本身可以基于函數(shù)參數(shù)提供唯一值,但是針對形形色色的不同業(yè)務來說,這肯定是不夠用的,需要提供用戶可以自定義參數(shù)序列化。


        //?如果配置中有?normalizer?函數(shù),直接使用,否則使用默認函數(shù)
        const?normalizer?=?options?.normalizer????generateKey

        return?new?Proxy(fn,?{
        ??//?@ts-ignore
        ??cache,
        ??apply(target,?thisArg,?argsList:?any[])?{
        ????const?cache:?Map?=?(this?as?any).cache
        ????
        ????//?根據(jù)格式化函數(shù)生成唯一數(shù)值
        ????const?cacheKey:?string?=?normalizer(argsList);
        ????
        ????if?(!cache.has(cacheKey))
        ??????cache.set(cacheKey,?target.apply(thisArg,?argsList));
        ????return?cache.get(cacheKey);
        ??}
        });


        添加 Promise 緩存


        在之前的博客中,提到緩存數(shù)據(jù)的弊端。同一時刻多次調(diào)用,會因為請求未返回而進行多次請求。所以我們也需要添加關于 Promise 的緩存。


        if?(!currentCache.has(cacheKey)){
        ??let?result?=?target.apply(thisArg,?argsList)
        ??
        ??//?如果是 promise 則緩存 promise,簡單判斷!
        ??//?如果當前函數(shù)有?then?則是?Promise
        ??if?(result?.then)?{
        ????result?=?Promise.resolve(result).catch(error?=>?{
        ??????//?發(fā)生錯誤,刪除當前?promise,否則會引發(fā)二次錯誤
        ??????//?由于異步,所以當前?delete?調(diào)用一定在?set?之后,
        ??????currentCache.delete(cacheKey)
        ????
        ??????//?把錯誤衍生出去
        ??????return?Promise.reject(error)
        ????})
        ??}
        ??currentCache.set(cacheKey,?result);
        }
        return?currentCache.get(cacheKey);


        此時,我們不但可以緩存數(shù)據(jù),還可以緩存 Promise 數(shù)據(jù)請求。


        添加過期刪除功能


        我們可以在數(shù)據(jù)中添加當前緩存時的時間戳,在生成數(shù)據(jù)時候添加。


        //?緩存項
        export?default?class?ExpiredCacheItem?{
        ??data:?V;
        ??cacheTime:?number;

        ??constructor(data:?V)?{
        ????this.data?=?data
        ????//?添加系統(tǒng)時間戳
        ????this.cacheTime?=?(new?Date()).getTime()
        ??}
        }

        //?編輯?Map?緩存中間層,判斷是否過期
        isOverTime(name:?string)?{
        ??const?data?=?this.cacheMap.get(name)

        ??//?沒有數(shù)據(jù)(因為當前保存的數(shù)據(jù)是?ExpiredCacheItem),所以我們統(tǒng)一看成功超時
        ??if?(!data)?return?true

        ??//?獲取系統(tǒng)當前時間戳
        ??const?currentTime?=?(new?Date()).getTime()

        ??//?獲取當前時間與存儲時間的過去的秒數(shù)
        ??const?overTime?=?currentTime?-?data.cacheTime

        ??//?如果過去的秒數(shù)大于當前的超時時間,也返回?null?讓其去服務端取數(shù)據(jù)
        ??if?(Math.abs(overTime)?>?this.timeout)?{
        ????//?此代碼可以沒有,不會出現(xiàn)問題,但是如果有此代碼,再次進入該方法就可以減少判斷。
        ????this.cacheMap.delete(name)
        ????return?true
        ??}

        ??//?不超時
        ??return?false
        }

        //?cache?函數(shù)有數(shù)據(jù)
        has(name:?string)?{
        ??//?直接判斷在?cache?中是否超時
        ??return?!this.isOverTime(name)
        }


        到達這一步,我們可以做到之前博客所描述的所有功能。不過,如果到這里就結束的話,太不過癮了。我們繼續(xù)學習其他庫的功能來優(yōu)化我的功能庫。


        添加手動管理


        通常來說,這些緩存庫都會有手動管理的功能,所以這里我也提供了手動管理緩存以便業(yè)務管理。這里我們使用 Proxy get 方法來攔截屬性讀取。


        return?new?Proxy(fn,?{
        ??//?@ts-ignore
        ??cache,
        ??get:?(target:?TargetFun,?property:?string)?=>?{
        ????
        ????//?如果配置了手動管理
        ????if?(options?.manual)?{
        ??????const?manualTarget?=?getManualActionObjFormCache(cache)
        ??????
        ??????//?如果當前調(diào)用的函數(shù)在當前對象中,直接調(diào)用,沒有的話訪問原對象
        ??????//?即使當前函數(shù)有該屬性或者方法也不考慮,誰讓你配置了手動管理呢。
        ??????if?(property?in?manualTarget)?{
        ????????return?manualTarget[property]
        ??????}
        ????}
        ???
        ????//?當前沒有配置手動管理,直接訪問原對象
        ????return?target[property]
        ??},
        }


        export?default?function?getManualActionObjFormCache(
        ??cache:?MemoizeCache
        ):?CacheMap?{
        ??const?manualTarget?=?Object.create(null)
        ??
        ??//?通過閉包添加?set?get?delete?clear?等?cache?操作
        ??manualTarget.set?=?(key:?string?|?object,?val:?V)?=>?cache.set(key,?val)
        ??manualTarget.get?=?(key:?string?|?object)?=>?cache.get(key)
        ??manualTarget.delete?=?(key:?string?|?object)?=>?cache.delete(key)
        ??manualTarget.clear?=?()?=>?cache.clear!()
        ??
        ??return?manualTarget
        }


        當前情況并不復雜,我們可以直接調(diào)用,復雜的情況下還是建議使用 Reflect 。


        添加 WeakMap


        我們在使用 cache 時候,我們同時也可以提供 WeakMap ( WeakMap 沒有 clear 和 size 方法),這里我提取了 BaseCache 基類。


        export?default?class?BaseCache?{
        ??readonly?weak:?boolean;
        ??cacheMap:?MemoizeCache

        ??constructor(weak:?boolean?=?false)?{
        ????//?是否使用?weakMap
        ????this.weak?=?weak
        ????this.cacheMap?=?this.getMapOrWeakMapByOption()
        ??}

        ??//?根據(jù)配置獲取?Map?或者?WeakMap
        ??getMapOrWeakMapByOption():?Map?|?WeakMap??{
        ????return?this.weak???new?WeakMap()?:?new?Map()
        ??}
        }


        之后,我添加各種類型的緩存類都以此為基類。


        添加清理函數(shù)


        在緩存進行刪除時候需要對值進行清理,需要用戶提供 dispose 函數(shù)。該類繼承 BaseCache 同時提供 dispose 調(diào)用。


        export?const?defaultDispose:?DisposeFun?=?()?=>?void?0

        export?default?class?BaseCacheWithDispose?extends?BaseCache?{
        ??readonly?weak:?boolean
        ??readonly?dispose:?DisposeFun

        ??constructor(weak:?boolean?=?false,?dispose:?DisposeFun?=?defaultDispose)?{
        ????super(weak)
        ????this.weak?=?weak
        ????this.dispose?=?dispose
        ??}

        ??//?清理單個值(調(diào)用?delete?前調(diào)用)
        ??disposeValue(value:?V?|?undefined):?void?{
        ????if?(value)?{
        ??????this.dispose(value)
        ????}
        ??}

        ??//?清理所有值(調(diào)用?clear?方法前調(diào)用,如果當前?Map?具有迭代器)
        ??disposeAllValue(cacheMap:?MemoizeCache):?void?{
        ????for?(let?mapValue?of?(cacheMap?as?any))?{
        ??????this.disposeValue(mapValue?.[1])
        ????}
        ??}
        }


        當前的緩存如果是 WeakMap,是沒有 clear 方法和迭代器的。個人想要添加中間層來完成這一切(還在考慮,目前沒有做)。如果 WeakMap 調(diào)用 clear 方法時,我是直接提供新的 WeakMap 。


        clear()?{
        ??if?(this.weak)?{
        ????this.cacheMap?=?this.getMapOrWeakMapByOption()
        ??}?else?{
        ????this.disposeAllValue(this.cacheMap)
        ????this.cacheMap.clear!()
        ??}
        }


        添加計數(shù)引用


        在學習其他庫?memoizee?的過程中,我看到了如下用法:


        memoized?=?memoize(fn,?{?refCounter:?true?});

        memoized("foo",?3);?//?refs:?1
        memoized("foo",?3);?//?Cache?hit,?refs:?2
        memoized("foo",?3);?//?Cache?hit,?refs:?3
        memoized.deleteRef("foo",?3);?//?refs:?2
        memoized.deleteRef("foo",?3);?//?refs:?1
        memoized.deleteRef("foo",?3);?//?refs:?0,清除?foo?的緩存
        memoized("foo",?3);?//?Re-executed,?refs:?1


        于是我有樣學樣,也添加了 RefCache。


        export?default?class?RefCache?extends?BaseCacheWithDispose?implements?CacheMap?{
        ????//?添加?ref?計數(shù)
        ??cacheRef:?MemoizeCache

        ??constructor(weak:?boolean?=?false,?dispose:?DisposeFun?=?()?=>?void?0)?{
        ????super(weak,?dispose)
        ????//?根據(jù)配置生成?WeakMap?或者?Map
        ????this.cacheRef?=?this.getMapOrWeakMapByOption()
        ??}
        ??

        ??//?get?has?clear?等相同。不列出
        ??
        ??delete(key:?string?|?object):?boolean?{
        ????this.disposeValue(this.get(key))
        ????this.cacheRef.delete(key)
        ????this.cacheMap.delete(key)
        ????return?true;
        ??}


        ??set(key:?string?|?object,?value:?V):?this?{
        ????this.cacheMap.set(key,?value)
        ????//?set?的同時添加?ref
        ????this.addRef(key)
        ????return?this
        ??}

        ??//?也可以手動添加計數(shù)
        ??addRef(key:?string?|?object)?{
        ????if?(!this.cacheMap.has(key))?{
        ??????return
        ????}
        ????const?refCount:?number?|?undefined?=?this.cacheRef.get(key)
        ????this.cacheRef.set(key,?(refCount????0)?+?1)
        ??}

        ??getRefCount(key:?string?|?object)?{
        ????return?this.cacheRef.get(key)????0
        ??}

        ??deleteRef(key:?string?|?object):?boolean?{
        ????if?(!this.cacheMap.has(key))?{
        ??????return?false
        ????}

        ????const?refCount:?number?=?this.getRefCount(key)

        ????if?(refCount?<=?0)?{
        ??????return?false
        ????}

        ????const?currentRefCount?=?refCount?-?1
        ????
        ????//?如果當前?refCount?大于?0,?設置,否則清除
        ????if?(currentRefCount?>?0)?{
        ??????this.cacheRef.set(key,?currentRefCount)
        ????}?else?{
        ??????this.cacheRef.delete(key)
        ??????this.cacheMap.delete(key)
        ????}
        ????return?true
        ??}
        }


        同時修改 proxy 主函數(shù):


        if?(!currentCache.has(cacheKey))?{
        ??let?result?=?target.apply(thisArg,?argsList)

        ??if?(result?.then)?{
        ????result?=?Promise.resolve(result).catch(error?=>?{
        ??????currentCache.delete(cacheKey)
        ??????return?Promise.reject(error)
        ????})
        ??}
        ??currentCache.set(cacheKey,?result);

        ??//?當前配置了?refCounter
        }?else?if?(options?.refCounter)?{
        ??//?如果被再次調(diào)用且當前已經(jīng)緩存過了,直接增加???????
        ??currentCache.addRef?.(cacheKey)
        }


        添加 LRU


        LRU 的英文全稱是 Least Recently Used,也即最不經(jīng)常使用。相比于其他的數(shù)據(jù)結構進行緩存,LRU 無疑更加有效。


        這里考慮在添加 maxAge 的同時也添加 max 值 (這里我利用兩個 Map 來做 LRU,雖然會增加一定的內(nèi)存消耗,但是性能更好)。


        如果當前的此時保存的數(shù)據(jù)項等于 max ,我們直接把當前 cacheMap 設為 oldCacheMap,并重新 new cacheMap。


        set(key:?string?|?object,?value:?V)?{
        ??const?itemCache?=?new?ExpiredCacheItem(value)
        ??//?如果之前有值,直接修改
        ??this.cacheMap.has(key)???this.cacheMap.set(key,?itemCache)?:?this._set(key,?itemCache);
        ??return?this
        }

        private?_set(key:?string?|?object,?value:?ExpiredCacheItem)?{
        ??this.cacheMap.set(key,?value);
        ??this.size++;

        ??if?(this.size?>=?this.max)?{
        ????this.size?=?0;
        ????this.oldCacheMap?=?this.cacheMap;
        ????this.cacheMap?=?this.getMapOrWeakMapByOption()
        ??}
        }


        重點在與獲取數(shù)據(jù)時候,如果當前的 cacheMap 中有值且沒有過期,直接返回,如果沒有,就去 oldCacheMap 查找,如果有,刪除老數(shù)據(jù)并放入新數(shù)據(jù)(使用 _set 方法),如果都沒有,返回 undefined.


        get(key:?string?|?object):?V?|?undefined?{
        ??//?如果?cacheMap?有,返回?value
        ??if?(this.cacheMap.has(key))?{
        ????const?item?=?this.cacheMap.get(key);
        ????return?this.getItemValue(key,?item!);
        ??}

        ??//?如果?oldCacheMap?里面有
        ??if?(this.oldCacheMap.has(key))?{
        ????const?item?=?this.oldCacheMap.get(key);
        ????//?沒有過期
        ????if?(!this.deleteIfExpired(key,?item!))?{
        ??????//?移動到新的數(shù)據(jù)中并刪除老數(shù)據(jù)
        ??????this.moveToRecent(key,?item!);
        ??????return?item!.data?as?V;
        ????}
        ??}
        ??return?undefined
        }


        private?moveToRecent(key:?string?|?object,?item:?ExpiredCacheItem)?{
        ??//?老數(shù)據(jù)刪除
        ??this.oldCacheMap.delete(key);
        ??
        ??//?新數(shù)據(jù)設定,重點?。。?!如果當前設定的數(shù)據(jù)等于?max,清空?oldCacheMap,如此,數(shù)據(jù)不會超過?max
        ??this._set(key,?item);
        }

        private?getItemValue(key:?string?|?object,?item:?ExpiredCacheItem):?V?|?undefined?{
        ??//?如果當前設定了?maxAge?就查詢,否則直接返回
        ??return?this.maxAge???this.getOrDeleteIfExpired(key,?item)?:?item?.data;
        }
        ??
        ??
        private?getOrDeleteIfExpired(key:?string?|?object,?item:?ExpiredCacheItem):?V?|?undefined?{
        ??const?deleted?=?this.deleteIfExpired(key,?item);
        ??return?!deleted???item.data?:?undefined;
        }
        ??
        private?deleteIfExpired(key:?string?|?object,?item:?ExpiredCacheItem)?{
        ??if?(this.isOverTime(item))?{
        ????return?this.delete(key);
        ??}
        ??return?false;
        }


        整理 memoize 函數(shù)


        事情到了這一步,我們就可以從之前的代碼細節(jié)中解放出來了,看看基于這些功能所做出的接口與主函數(shù)。


        //?面向接口,無論后面還會不會增加其他類型的緩存類
        export?interface?BaseCacheMap?{
        ??delete(key:?K):?boolean;

        ??get(key:?K):?V?|?undefined;

        ??has(key:?K):?boolean;

        ??set(key:?K,?value:?V):?this;

        ??clear?():?void;

        ??addRef?(key:?K):?void;

        ??deleteRef?(key:?K):?boolean;
        }

        //?緩存配置
        export?interface?MemoizeOptions?{
        ??/**?序列化參數(shù)?*/
        ??normalizer?:?(args:?any[])?=>?string;
        ??/**?是否使用?WeakMap?*/
        ??weak?:?boolean;
        ??/**?最大毫秒數(shù),過時刪除?*/
        ??maxAge?:?number;
        ??/**?最大項數(shù),超過刪除??*/
        ??max?:?number;
        ??/**?手動管理內(nèi)存?*/
        ??manual?:?boolean;
        ??/**?是否使用引用計數(shù)??*/
        ??refCounter?:?boolean;
        ??/**?緩存刪除數(shù)據(jù)時期的回調(diào)?*/
        ??dispose?:?DisposeFun;
        }

        //?返回的函數(shù)(攜帶一系列方法)
        export?interface?ResultFun?extends?Function?{
        ??delete?(key:?string?|?object):?boolean;

        ??get?(key:?string?|?object):?V?|?undefined;

        ??has?(key:?string?|?object):?boolean;

        ??set?(key:?string?|?object,?value:?V):?this;

        ??clear?():?void;

        ??deleteRef?():?void
        }


        最終的 memoize 函數(shù)其實和最開始的函數(shù)差不多,只做了 3 件事


        • 檢查參數(shù)并拋出錯誤
        • 根據(jù)參數(shù)獲取合適的緩存
        • 返回代理

        export?default?function?memoize(fn:?TargetFun,?options?:?MemoizeOptions):?ResultFun?{
        ??//?檢查參數(shù)并拋出錯誤
        ??checkOptionsThenThrowError(options)

        ??//?修正序列化函數(shù)
        ??const?normalizer?=?options?.normalizer????generateKey

        ??let?cache:?MemoizeCache?=?getCacheByOptions(options)

        ??//?返回代理
        ??return?new?Proxy(fn,?{
        ????//?@ts-ignore
        ????cache,
        ????get:?(target:?TargetFun,?property:?string)?=>?{
        ??????//?添加手動管理
        ??????if?(options?.manual)?{
        ????????const?manualTarget?=?getManualActionObjFormCache(cache)
        ????????if?(property?in?manualTarget)?{
        ??????????return?manualTarget[property]
        ????????}
        ??????}
        ??????return?target[property]
        ????},
        ????apply(target,?thisArg,?argsList:?any[]):?V?{

        ??????const?currentCache:?MemoizeCache?=?(this?as?any).cache

        ??????const?cacheKey:?string?|?object?=?getKeyFromArguments(argsList,?normalizer,?options?.weak)

        ??????if?(!currentCache.has(cacheKey))?{
        ????????let?result?=?target.apply(thisArg,?argsList)

        ??????
        ????????if?(result?.then)?{
        ??????????result?=?Promise.resolve(result).catch(error?=>?{
        ????????????currentCache.delete(cacheKey)
        ????????????return?Promise.reject(error)
        ??????????})
        ????????}
        ????????currentCache.set(cacheKey,?result);
        ??????}?else?if?(options?.refCounter)?{
        ????????currentCache.addRef?.(cacheKey)
        ??????}
        ??????return?currentCache.get(cacheKey)?as?V;
        ????}
        ??})?as?any
        }

        完整代碼在 memoizee-proxy 中。大家自行操作與把玩。
        https://github.com/wsafight/memoizee-proxy



        下一步


        測試


        測試覆蓋率不代表一切,但是在實現(xiàn)庫的過程中,JEST 測試庫給我提供了大量的幫助,它幫助我重新思考每一個類以及每一個函數(shù)應該具有的功能與參數(shù)校驗。之前的代碼我總是在項目的主入口進行校驗,對于每個類或者函數(shù)的參數(shù)沒有深入思考。事實上,這個健壯性是不夠的。因為你不能決定用戶怎么使用你的庫。

        Proxy 深入


        事實上,代理的應用場景是不可限量的。這一點,ruby 已經(jīng)驗證過了(可以去學習《ruby 元編程》)。

        開發(fā)者使用它可以創(chuàng)建出各種編碼模式,比如(但遠遠不限于)跟蹤屬性訪問、隱藏屬性、阻止修改或刪除屬性、函數(shù)參數(shù)驗證、構造函數(shù)參數(shù)驗證、數(shù)據(jù)綁定,以及可觀察對象。

        當然,Proxy 雖然來自于 ES6 ,但該 API 仍需要較高的瀏覽器版本,雖然有 proxy-pollfill ,但畢竟提供功能有限。不過已經(jīng) 2021,相信深入學習 Proxy 也是時機了。

        深入緩存


        緩存是有害的!這一點毋庸置疑。但是它實在太快了!所以我們要更加理解業(yè)務,哪些數(shù)據(jù)需要緩存,理解那些數(shù)據(jù)可以使用緩存。

        當前書寫的緩存僅僅只是針對與一個方法,之后寫的項目是否可以更細粒度的結合返回數(shù)據(jù)?還是更往上思考,寫出一套緩存層?

        小步開發(fā)


        在開發(fā)該項目的過程中,我采用小步快跑的方式,不斷返工。最開始的代碼,也僅僅只到了添加過期刪除功能那一步。

        但是當我每次完成一個新的功能后,重新開始整理庫的邏輯與流程,爭取每一次的代碼都足夠優(yōu)雅。同時因為我不具備第一次編寫就能通盤考慮的能力。不過希望在今后的工作中,不斷進步。這樣也能減少代碼的返工。



        其他


        函數(shù)創(chuàng)建


        事實上,我在為當前庫添加手動管理時候,考慮過直接復制函數(shù),因為函數(shù)本身是一個對象。同時為當前函數(shù)添加 set 等方法。但是沒有辦法把作用域鏈拷貝過去。

        雖然沒能成功,但是也學到了一些知識,這里也提供兩個創(chuàng)建函數(shù)的代碼。

        我們在創(chuàng)建函數(shù)時候基本上會利用 new Function 創(chuàng)建函數(shù),但是瀏覽器沒有提供可以直接創(chuàng)建異步函數(shù)的構造器,我們需要手動獲取。

        AsyncFunction?=?(async?x?=>?x).constructor

        foo?=?new?AsyncFunction('x,?y,?p',?'return?x?+?y?+?await?p')

        foo(1,2,?Promise.resolve(3)).then(console.log)?//?6

        對于全局函數(shù),我們也可以直接 fn.toString() 來創(chuàng)建函數(shù),這時候異步函數(shù)也可以直接構造的。

        function?cloneFunction(fn:?(...args:?any[])?=>?T):?(...args:?any[])?=>?T?{
        ??return?new?Function('return?'+?fn.toString())();
        }




        點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復“?入群?”即可加入我們的技術交流群,收獲更多的技術文章~

        -?END -


        瀏覽 42
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产伊人久久久 | 欧美黄色操逼视频电影 | 女人色天堂 | 91成人电影在线观看 | 四虎影院久久 | 在线免费看黄片 | 蜜桃AV噜噜一区二区三区四区五区 | 太粗太深太大受不了了视频 | 色视频在线观看 | 中文字幕乱码亚洲无线码按摩 |