自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文帶你使用 Swift 實現(xiàn) Promise

開發(fā) 前端
我最近在找如何使用 Swift 實現(xiàn) Promise 的資料,因為沒找到好的文章,所以我想自己寫一篇。通過本文,我們將實現(xiàn)自己的 Promise 類型,以便明了其背后的邏輯。

[[421175]]

前言

我最近在找如何使用 Swift 實現(xiàn) Promise 的資料,因為沒找到好的文章,所以我想自己寫一篇。通過本文,我們將實現(xiàn)自己的 Promise 類型,以便明了其背后的邏輯。

要注意這個實現(xiàn)完全不適合生產(chǎn)環(huán)境。例如,我們的 Promise 沒有提供任何錯誤機制,也沒有覆蓋線程相關的場景。我會在文章的后面提供一些有用的資源以及完整實現(xiàn)的鏈接,以饗愿深入挖掘的讀者。

注:為了讓本教程更有趣一點,我選擇使用 TDD 來進行介紹。我們會先寫測試,然后確保它們一個個通過。

第一個測試

先寫第一個測試:

  1. test(named: "0. Executor function is called immediately") { assert, done in 
  2.     var string: String = "" 
  3.     _ = Promise { string = "foo" } 
  4.     assert(string == "foo"
  5.     done() 

通過此測試,我們想實現(xiàn):傳遞一個函數(shù)給 Promise 的初始化函數(shù),并立即調用此函數(shù)。

注:我們沒有使用任何測試框架,僅僅使用一個自定義的test方法,它在 Playground 中模擬斷言(gist[1])。

當我們運行 Playground,編譯器會報錯:

  1. error: Promise.playground:41:9: error: use of unresolved identifier 'Promise' 
  2.     _ = Promise { string = "foo" } 
  3.         ^~~~~~~ 

合理,我們需要定義 Promise 類。

  1. class Promise { 
  2.  

再運行,錯誤變?yōu)椋?/p>

  1. error: Promise.playground:44:17: error: argument passed to call that takes no arguments 
  2.     _ = Promise { string = "foo" } 
  3.                 ^~~~~~~~~~~~~~~~~~ 

我們必須定義一個初始化函數(shù),它接受一個閉包作為參數(shù)。而且這個閉包應該被立即調用。

  1. class Promise { 
  2.  
  3.     init(executor: () -> Void) { 
  4.         executor() 
  5.     } 

由此,我們通過第一個測試。目前我們還沒有寫出什么值得夸耀的東西,但耐心一點,我們的實現(xiàn)將在下一節(jié)繼續(xù)增長。

  1. • Test 0. Executor function is called immediately passed  

我們先將此測試注釋掉,因為將來的 Promise 實現(xiàn)會變得有些不同。

最低限度

第二個測試如下:

  1. test(named: "1.1 Resolution handler is called when promise is resolved sync") { assert, done in 
  2.     let string: String = "foo" 
  3.     let promise = Promise<String> { resolve in 
  4.         resolve(string) 
  5.     } 
  6.     promise.then { (value: String) in 
  7.         assert(string == value) 
  8.         done() 
  9.     } 

這個測試挺簡單,但我們添加了一些新內(nèi)容到 Promise 類。我們創(chuàng)建的這個 promise 有一個 resolution handler(即閉包的resolve參數(shù)),之后立即調用它(傳遞一個 value)。然后,我們使用 promise 的 then 方法來訪問 value 并用斷言確保其值。

在開始實現(xiàn)之前,我們需要引入另外一個不太一樣的測試。

  1. test(named: "1.2 Resolution handler is called when promise is resolved async") { assert, done in 
  2.     let string: String = "foo" 
  3.     let promise = Promise<String> { resolve in 
  4.         after(0.1) { 
  5.             resolve(string) 
  6.         } 
  7.     } 
  8.     promise.then { (value: String) in 
  9.         assert(string == value) 
  10.         done() 
  11.     } 

不同于測試 1.1,這里的resove方法被延遲調用。這意味著,在then里,value 不會立馬可用(因為 0.1 秒的延遲,調用then時,resolve還未被調用)。

我們開始理解這里的“問題”。我們必須處理異步。

我們的 promise 是一個狀態(tài)機。當它被創(chuàng)建時,promise 處于pending狀態(tài)。一旦resolve方法被調用(與一個 value),我們的 promise 將轉到resolved狀態(tài),并存儲這個 value。

then方法可在任意時刻被調用,而不管 promise 的內(nèi)部狀態(tài)(即不管 promise 是否已有一個 value)。當這個 promise 處于pending狀態(tài)時,我們調用then,value 將不可用,因此,我們需要存儲此回調。之后一旦 promise 變成resolved,我們就能使用 resolved value 來觸發(fā)同樣的回調。

現(xiàn)在我們對要實現(xiàn)的東西有了更好的理解,那就先以修復編譯器的報錯開始。

  1. error: Promise.playground:54:19: error: cannot specialize non-generic type 'Promise' 
  2.     let promise = Promise<String> { resolve in 
  3.                   ^      ~~~~~~~~ 

我們必須給Promise類型添加泛型。誠然,一個 promise 是這樣的東西:它關聯(lián)著一個預定義的類型,并能在被解決時,將一個此類型的 value 保留住。

  1. class Promise<Value> { 
  2.  
  3.     init(executor: () -> Void) { 
  4.         executor() 
  5.     } 

現(xiàn)在錯誤為:

  1. error: Promise.playground:54:37: error: contextual closure type '() -> Void' expects 0 arguments, but 1 was used in closure body 
  2.     let promise = Promise<String> { resolve in 
  3.                                     ^ 

我們必須提供一個resolve函數(shù)傳遞給初始化函數(shù)(即 executor)。

  1. class Promise<Value> { 
  2.  
  3.     init(executor: (_ resolve: (Value) -> Void) -> Void) { 
  4.         executor() 
  5.     } 

注意這個 resolve 參數(shù)是一個函數(shù),它消耗一個 value:(Value) -> Void。一旦 value 被確定,這個函數(shù)將被外部世界調用。

編譯器依然不開心,因為我們需要提供一個resolve函數(shù)給executor。讓我們創(chuàng)建一個private的吧。

  1. class Promise<Value> { 
  2.  
  3.     init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) { 
  4.         executor(resolve) 
  5.     } 
  6.  
  7.     private func resolve(_ value: Value) -> Void { 
  8.         // To implement 
  9.         // This will be called by the outside world when a value is determined 
  10.     } 

我們將在稍后實現(xiàn)resolve,當所有錯誤都被解決時。

下一個錯誤很簡單,方法then還未定義。

  1. error: Promise.playground:61:5: error: value of type 'Promise<String>' has no member 'then' 
  2.     promise.then { (value: String) in 
  3.     ^~~~~~~ ~~~~ 

讓我們修復之。

  1. class Promise<Value> { 
  2.  
  3.     init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) { 
  4.         executor(resolve) 
  5.     } 
  6.  
  7.     func then(onResolved: @escaping (Value) -> Void) { 
  8.         // To implement 
  9.     } 
  10.  
  11.     private func resolve(_ value: Value) -> Void { 
  12.         // To implement 
  13.     } 

現(xiàn)在編譯器開心了,讓我們回到開始的地方。

我們之前說過一個Promise就是一個狀態(tài)機,它有一個pending狀態(tài)和一個resolved狀態(tài)。我們可以使用 enum 來定義它們。

  1. enum State<T> { 
  2.     case pending 
  3.     case resolved(T) 

Swift 的美妙讓我們可以直接存儲 promise 的 value 在 enum 中。

現(xiàn)在我們需要在Promise的實現(xiàn)中定義一個狀態(tài),其默認值為.pending。我們還需要一個私有函數(shù),它能在當前還處于.pending狀態(tài)時更新狀態(tài)。

  1. class Promise<Value> { 
  2.  
  3.     enum State<T> { 
  4.         case pending 
  5.         case resolved(T) 
  6.     } 
  7.  
  8.     private var state: State<Value> = .pending 
  9.  
  10.     init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) { 
  11.         executor(resolve) 
  12.     } 
  13.  
  14.     func then(onResolved: @escaping (Value) -> Void) { 
  15.         // To implement 
  16.     } 
  17.  
  18.     private func resolve(_ value: Value) -> Void { 
  19.         // To implement 
  20.     } 
  21.  
  22.     private func updateState(to newState: State<Value>) { 
  23.         guard case .pending = state else { return } 
  24.         state = newState 
  25.     } 

注意updateState(to:)函數(shù)先檢查了當前處于.pending狀態(tài)。如果 promise 已經(jīng)處于.resolved狀態(tài),那它就不能再變成其他狀態(tài)了。

現(xiàn)在是時候在必要時更新 promise 的狀態(tài),即,當resolve函數(shù)被外部世界傳遞 value 調用時。

  1. private func resolve(_ value: Value) -> Void { 
  2.     updateState(to: .resolved(value)) 

快好了,只缺少 then 方法還未實現(xiàn)。我們說過必須存儲回調,并在 promise 被解決時調用回調。這就來實現(xiàn)之。

  1. class Promise<Value> { 
  2.  
  3.     enum State<T> { 
  4.         case pending 
  5.         case resolved(T) 
  6.     } 
  7.  
  8.     private var state: State<Value> = .pending 
  9.     // we store the callback as an instance variable 
  10.     private var callback: ((Value) -> Void)? 
  11.  
  12.     init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) { 
  13.         executor(resolve) 
  14.     } 
  15.  
  16.     func then(onResolved: @escaping (Value) -> Void) { 
  17.         // store the callback in all cases 
  18.         callback = onResolved 
  19.         // and trigger it if needed 
  20.         triggerCallbackIfResolved() 
  21.     } 
  22.  
  23.     private func resolve(_ value: Value) -> Void { 
  24.         updateState(to: .resolved(value)) 
  25.     } 
  26.  
  27.     private func updateState(to newState: State<Value>) { 
  28.         guard case .pending = state else { return } 
  29.         state = newState 
  30.         triggerCallbackIfResolved() 
  31.     } 
  32.  
  33.     private func triggerCallbackIfResolved() { 
  34.         // the callback can be triggered only if we have a value, 
  35.         // meaning the promise is resolved 
  36.         guard case let .resolved(value) = state else { return } 
  37.         callback?(value) 
  38.         callback = nil 
  39.     } 

我們定義了一個實例變量callback,以在 promise 處于.pending狀態(tài)時保留回調。同時我們創(chuàng)建一個方法triggerCallbackIfResolved,它先檢查狀態(tài)是否為.resolved,然后傳遞拆包的 value 給回調。這個方法在兩個地方被調用。一個是then方法中,如果 promise 已經(jīng)在調用then時被解決。另一個在updateState方法中,因為那是 promise 更新其內(nèi)部狀態(tài)從.pending到.resolved的地方。

有了這些修改,我們的測試就成功通過了。

  1. • Test 1.1 Resolution handler is called when promise is resolved sync passed (1 assertions) 
  2. • Test 1.2 Resolution handler is called when promise is resolved async passed (1 assertions) 

我們走對了路,但我們還需要做出一點改變,以得到一個真正的Promise實現(xiàn)。先來看看測試。

  1. test(named: "2.1 Promise supports many resolution handlers sync") { assert, done in 
  2.     let string: String = "foo" 
  3.     let promise = Promise<String> { resolve in 
  4.         resolve(string) 
  5.     } 
  6.     promise.then { value in 
  7.         assert(string == value) 
  8.     } 
  9.     promise.then { value in 
  10.         assert(string == value) 
  11.         done() 
  12.     } 
  1. test(named: "2.2 Promise supports many resolution handlers async") { assert, done in 
  2.     let string: String = "foo" 
  3.     let promise = Promise<String> { resolve in 
  4.         after(0.1) { 
  5.             resolve(string) 
  6.         } 
  7.     } 
  8.     promise.then { value in 
  9.         assert(string == value) 
  10.     } 
  11.     promise.then { value in 
  12.         assert(string == value) 
  13.         done() 
  14.     } 

這回我們對每個 promise 都調用了兩次then。

先看看測試輸出。

  1. • Test 2.1 Promise supports many resolution handlers sync passed (2 assertions) 
  2. • Test 2.2 Promise supports many resolution handlers async passed (1 assertions) 

雖然測試通過了,但你可能也注意問題。測試 2.2 只有一個斷言,但應該是兩個。

如果我們思考一下,這其實符合邏輯。誠然,在異步的測試 2.2 中,當?shù)谝粋€then被調用時,promise 還處于.pending狀態(tài)。如我們之前所見,我們存儲了第一次then的回調。但當我們第二次調用then時,promise 還是沒有被解決,依然處于.pending狀態(tài),于是,我們將回調擦除換成了新的。只有第二個回調會在將來被執(zhí)行,第一個被忘記了。這使得測試雖然通過,但只有一個斷言而不是兩個。

解決辦法也很簡單,就是存儲一個回調的數(shù)組,并在promise被解決時觸發(fā)它們。

讓我們更新一下。

  1. class Promise<Value> { 
  2.  
  3.     enum State<T> { 
  4.         case pending 
  5.         case resolved(T) 
  6.     } 
  7.  
  8.     private var state: State<Value> = .pending 
  9.     // We now store an array instead of a single function 
  10.     private var callbacks: [(Value) -> Void] = [] 
  11.  
  12.     init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) { 
  13.         executor(resolve) 
  14.     } 
  15.  
  16.     func then(onResolved: @escaping (Value) -> Void) { 
  17.         callbacks.append(onResolved) 
  18.         triggerCallbacksIfResolved() 
  19.     } 
  20.  
  21.     private func resolve(_ value: Value) -> Void { 
  22.         updateState(to: .resolved(value)) 
  23.     } 
  24.  
  25.     private func updateState(to newState: State<Value>) { 
  26.         guard case .pending = state else { return } 
  27.         state = newState 
  28.         triggerCallbacksIfResolved() 
  29.     } 
  30.  
  31.     private func triggerCallbacksIfResolved() { 
  32.         guard case let .resolved(value) = state else { return } 
  33.         // We trigger all the callbacks 
  34.         callbacks.forEach { callback in callback(value) } 
  35.         callbacks.removeAll() 
  36.     } 

測試通過,而且都有兩個斷言。

  1. • Test 2.1 Promise supports many resolution handlers sync passed (2 assertions) 
  2. • Test 2.2 Promise supports many resolution handlers async passed (2 assertions) 

恭喜!我們已經(jīng)創(chuàng)建了自己的Promise類。你已經(jīng)可以使用它來抽象異步邏輯,但它還有限制。

注:如果從全局來看,我們知道then可以被重命名為observe。它的目的是消費 promise 被解決后的 value,但它不返回什么。這意味著我們暫時沒法串聯(lián)多個 promise。

串聯(lián)多個 Promise

如果我們不能串聯(lián)多個 promise,那我們的Promise實現(xiàn)就不算完整。

先來看看測試,它將幫助我們實現(xiàn)這個特性。

  1. test(named: "3. Resolution handlers can be chained") { assert, done in 
  2.     let string: String = "foo" 
  3.     let promise = Promise<String> { resolve in 
  4.         after(0.1) { 
  5.             resolve(string) 
  6.         } 
  7.     } 
  8.     promise 
  9.         .then { value in 
  10.             return Promise<String> { resolve in 
  11.                 after(0.1) { 
  12.                     resolve(value + value) 
  13.                 } 
  14.             } 
  15.         } 
  16.         .then { value in // the "observe" previously defined 
  17.             assert(string + string == value) 
  18.             done() 
  19.         } 

如測試所見,第一個then創(chuàng)建了一個有新 value 的新Promise并返回了它。第二個then(我們前一節(jié)定義的,被稱為observe)被串聯(lián)在后面,它訪問新的 value(其將是"foofoo")。

我們很快在終端里看到錯誤。

  1. error: Promise.playground:143:10: error: value of tuple type '()' has no member 'then' 
  2.         .then { value in 
  3.          ^ 

我們必須創(chuàng)建一個then的重載,它接受一個能返回 promise 的函數(shù)。為了能夠串聯(lián)調用then,這個方法必須也返回一個promise。這個then的原型如下。

  1. func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> { 
  2.     // to implement 

注:細心的讀者可能已經(jīng)發(fā)現(xiàn),我們在給Promise實現(xiàn)flatMap。就如給Optional和Array定義flatMap一樣,我們也可以給Promise定義它。

困難來了。讓我們一步步看看這個“flatMap”的then要怎么實現(xiàn)。

  • 我們需要返回一個Promise
  • 誰給我們這樣一個 promise?onResolved 方法
  • 但onResolved 需要一個類型為Value的 value 為參數(shù)。我們該怎樣得到這個 value? 我們可以使用之前定義的then(或者說 “observe”) 來在其可用時訪問它

如果寫成代碼,大概如下:

  1. func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> { 
  2.     then { value in // the "observe" one 
  3.         let promise = onResolved(value) // `promise` is a Promise<NewValue> 
  4.         // problem: how do we return `promise` to the outside ?? 
  5.     } 
  6.     return // ??!! 

就快好了。但我們還有個小問題需要修復:這個promise變量被傳遞給then的閉包所限制。我們不能將其作為函數(shù)的返回值。

我們要使用的技巧是創(chuàng)建一個包裝Promise,它將執(zhí)行我們目前所寫的代碼,然后在promise變量解決時被同時解決。換句話說,當onResolved方法提供的 promise 被解決并從外部得到一個值,那包裝的 promise 也就被解決并得到同樣的值。

可能文字有些抽象,但如果我們寫成代碼,將看得更清楚:

  1. func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> { 
  2.     // We have to return a promise, so let's return a new one 
  3.     return Promise<NewValue> { resolve in 
  4.         // this is called immediately as seen in test 0. 
  5.         then { value in // the "observe" one 
  6.             let promise = onResolved(value) // `promise` is a Promise<NewValue> 
  7.             // `promise` has the same type of the Promise wrapper 
  8.             // we can make the wrapper resolves when the `promise` resolves 
  9.             // and gets a value 
  10.             promise.then { value in resolve(value) } 
  11.         } 
  12.     } 

如果我們整理一下代碼,我們將有這樣兩個方法:

  1. // observe 
  2. func then(onResolved: @escaping (Value) -> Void) { 
  3.     callbacks.append(onResolved) 
  4.     triggerCallbacksIfResolved() 
  5.  
  6. // flatMap 
  7. func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> { 
  8.     return Promise<NewValue> { resolve in 
  9.         then { value in 
  10.             onResolved(value).then(onResolved: resolve) 
  11.         } 
  12.     } 

最后,測試通過。

  1. • Test 3. Resolution handlers can be chained passed (1 assertions) 

串聯(lián)多個 value

如果你能給某個類型實現(xiàn)flatMap,那你就能利用flatMap為其實現(xiàn)map。對于我們的Promise來說,map該是什么樣子?

我們將使用如下測試:

  1. test(named: "4. Chaining works with non promise return values") { assert, done in 
  2.     let string: String = "foo" 
  3.     let promise = Promise<String> { resolve in 
  4.         after(0.1) { 
  5.             resolve(string) 
  6.         } 
  7.     } 
  8.     promise 
  9.         .then { value -> String in 
  10.             return value + value 
  11.         } 
  12.         .then { value in // the "observe" then 
  13.             assert(string + string == value) 
  14.             done() 
  15.         } 

注意第一個then沒有再返回一個Promise,而是將其接收的值做了一個變換。這個新的then就對應于我們想添加的map。

編譯器報錯說我們必須實現(xiàn)此方法。

  1. error: Promise.playground:174:26: error: declared closure result 'String' is incompatible with contextual type 'Void' 
  2.         .then { value -> String in 
  3.                          ^~~~~~ 
  4.                          Void 

這個方法很接近flatMap,唯一的不同是其參數(shù)onResolved函數(shù)返回一個NewValue而不是Promise。

  1. // map 
  2. func then<NewValue>(onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> { 
  3.     // to implement 

之前我們說可以利用flatMap實現(xiàn)map。在我們的情況里,我們看到我們需要返回一個Promise。如果我們使用這個“flatMap”的then,并創(chuàng)建一個promise,再以映射后的 value 來直接解決,我們就搞定了。讓我來證明之。

  1. // map 
  2. func then<NewValue>(onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> { 
  3.     return then { value in // the "flatMap" defined before 
  4.         // must return a Promise<NewValue> here 
  5.         // this promise directly resolves with the mapped value 
  6.         return Promise<NewValue> { resolve in 
  7.             let newValue = onResolved(value) 
  8.             resolve(newValue) 
  9.         } 
  10.     } 

再一次,測試通過。

  1. • Test 4. Chaining works with non promise return values passed (1 assertions) 

如果我們移除注釋,再看看我們做出了什么。我們有三個then方法的實現(xiàn),能被使用或串聯(lián)。

  1. // observe 
  2. func then(onResolved: @escaping (Value) -> Void) { 
  3.     callbacks.append(onResolved) 
  4.     triggerCallbacksIfResolved() 
  5.  
  6. // flatMap 
  7. func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> { 
  8.     return Promise<NewValue> { resolve in 
  9.         then { value in 
  10.             onResolved(value).then(onResolved: resolve) 
  11.         } 
  12.     } 
  13.  
  14. // map 
  15. func then<NewValue>(onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> { 
  16.     return then { value in 
  17.         return Promise<NewValue> { resolve in 
  18.             resolve(onResolved(value)) 
  19.         } 
  20.     } 

使用示例

實現(xiàn)告一段落。我們的Promise類已足夠完整來展示我們能夠用它做什么。

假設我們的app有一些用戶,結構如下:

  1. struct User { 
  2.     let id: Int 
  3.     let name: String 

假設我們還有兩個方法,一個獲取用戶id列表,另一個使用id獲取某個用戶。而且假設我們想顯示第一個用戶的名字。

通過我們的實現(xiàn),我們可以這樣做,使用之前定義的這三個then。

  1. func fetchIds() -> Promise<[Int]> { 
  2.     ... 
  3.  
  4. func fetchUser(id: Int) -> Promise<User> { 
  5.     ... 
  6.  
  7. fetchIds() 
  8.     .then { ids in // flatMap 
  9.         return fetchUser(id: ids[0]) 
  10.     } 
  11.     .then { user in // map 
  12.         return user.name 
  13.     } 
  14.     .then { name in // observe 
  15.         print(name
  16.     } 

代碼變得十分易讀、簡潔,而且沒有嵌套。

結論本文結束,希望你喜歡它。

參考資料

[1]gist:

https://gist.github.com/felginep/039ca3b21e4f0cabb1c06126d9164680

[2]Promises in Swift by Khanlou:

http://khanlou.com/2016/08/promises-in-swift/

[3]JavaScript Promises … In Wicked Detail:

https://www.mattgreer.org/articles/promises-in-wicked-detail/

[4]PromiseKit 6 Release Details:

https://promisekit.org/news/2018/02/PromiseKit-6.0-Released/

[5]TDD Implementation of Promises in JavaScript:

https://www.youtube.com/watch?v=C3kUMPtt4hY

[6]Implementing Promises in Swift:

https://felginep.github.io/2019-01-06/implementing-promises-in-swift

本文轉載自微信公眾號「Swift社區(qū)」

 

責任編輯:姜華 來源: Swift社區(qū)
相關推薦

2022-12-20 07:39:46

2023-11-20 08:18:49

Netty服務器

2023-12-21 17:11:21

Containerd管理工具命令行

2023-11-06 08:16:19

APM系統(tǒng)運維

2021-05-29 10:11:00

Kafa數(shù)據(jù)業(yè)務

2023-07-31 08:18:50

Docker參數(shù)容器

2022-11-11 19:09:13

架構

2024-10-10 09:12:10

Spring接口初始化

2022-07-18 21:53:46

RocketMQ廣播消息

2022-02-24 07:34:10

SSL協(xié)議加密

2023-11-08 08:15:48

服務監(jiān)控Zipkin

2023-10-27 08:15:45

2024-05-22 09:45:49

2021-09-13 22:34:56

區(qū)塊鏈新基建數(shù)字化轉型

2023-03-06 21:29:41

mmap技術操作系統(tǒng)

2022-05-16 10:49:28

網(wǎng)絡協(xié)議數(shù)據(jù)

2019-06-13 21:31:19

AI

2022-04-08 09:01:14

CSS自定義屬性前端

2020-11-27 09:40:53

Rollup前端代碼

2022-02-28 12:07:56

RxJS函數(shù)式
點贊
收藏

51CTO技術棧公眾號