LoginSignup
0
1

More than 1 year has passed since last update.

SwiftでPromiseを作る(async・awaitも作る)

Last updated at Posted at 2021-08-07

最近JSを初めまして、Promise・async・awaitに惚れました。
なのでホームフィールであるSwiftにこれを持ち込みたい!!!と思いで再実装を行ってみました。Promiseの理解の良い助けにもなりました。

今回は非同期の仕組みは実装せずcallback -> Promiseの変換ができるものを目指します。

Promiseを作る

まずPromiseの特徴として状態を内部に持つことがあるのでStateを作り、状態を保持しておきます。

class Promise<Output> {
    enum State {
        case pending
        case fulfilled(Output)
        case rejected(Error)
    }

    var state = State.pending
}

またfullfillrejectをそれぞれできるようにしておきます。

func fullfill(_ output: Output) {
    self.state = .fulfilled(output)
}
func reject(_ error: Error) {
    self.state = .rejected(error)
}

ここまでできたらイニシャライザを実装します。

init(_ handler: (@escaping (Output)->(), @escaping (Error)->())->()) {
    handler(self.fullfill, self.reject)
}

ここまでで状態を持ちfullfill or rejectできるPromiseの仕組みは完成です。
次に、JSのPromiseにおけるthenを実装していきます。

thenを受け取る

登録されたクロージャを保持しておくために、subscribeできるようにします。

class Promise {
    struct Subscriber {
        let resolve: (Output)->()
        let reject: (Failure)->()
    }

    var subscribers = [Subscriber]()

    func subscribe(_ resolve: @escaping (Output)->(), _ reject: @escaping (Failure)->()) {
        switch self.state {
        case .pending: 
            let subscriber = Subscriber(resolve: resolve, reject: reject)
            self.subscribers.append(subscriber)
        case .fulfilled(let output): resolve(output)
        case .rejected(let error): reject(error)
        }
    }
}

また、fullfillとrejectもsubscribeされたクロージャを実行するように、変更しましょう。

func fullfill(_ output: Output) {
    self.state = .fulfilled(output)
    for subscriber in subscribers { 
        subscriber.resolve(output) 
    }
    self.subscribers = []
}
func reject(_ error: Error) {
    self.state = .rejected(error)
    for subscriber in subscribers {
        subscriber.reject(error) 
    }
    self.subscribers = []
}

これで購読が可能になりました。
メソッドチェーンで購読できるようにextensionを追加してみましょう。

JSのthenはSwiftにおけるmap・flatMap・sinkの機能を同時に持っているので、これらを分けて考えます。

// Promiseのextension

func map<T>(_ tranceform: @escaping (Output)->T) -> Promise<T> {
    Promise<T> { resolve, reject in
        self.subscribe({ resolve(tranceform($0)) }, reject)
    }
}

func flatMap<T>(_ tranceform: @escaping (Output)->Promise<T>) -> Promise<T> {
    Promise<T> { resolve, reject in
        self.subscribe({ tranceform($0).sink(resolve, reject) }, reject)
    }
}

func sink(_ onFulfilled: @escaping (Output)->(), _ onRejected: @escaping (Error)->()) {
    self.subscribe(onFulfilled, onRejected)
}

またJSのcatchはSwiftのreplaceError相当なのでこれも実装しましょう。
(厳密にはreplaceError + map)

func replaceError(_ tranceform: @escaping (Error)->Output) -> Promise<Output> {
    Promise<Output> { resolve, _ in
        self.subscribe(resolve, { resolve(tranceform($0)) })
    }
}

完成!!!!!

Promise<String>{ resolve, _ in
    Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
        resolve("Hello World")
    }
}
.sink{ print($0) } // Hello World

async・await→(ちょっとお待ちを)

全体

public final class Promise<Output, Failure: Error> {
    private enum State {
        case pending
        case fulfilled(Output)
        case rejected(Failure)
    }
    private struct Subscriber {
        let resolve: (Output)->()
        let reject: (Failure)->()
    }

    private var state = State.pending
    private var subscribers = [Subscriber]()

    public init(_ handler: (@escaping (Output)->(), @escaping (Failure)->())->()) {
        handler(fullfill, reject)
    }

    private func subscribe(_ resolve: @escaping (Output)->(), _ reject: @escaping (Failure)->()) {
        switch self.state {
        case .pending: self.subscribers.append(Subscriber(resolve: resolve, reject: reject))
        case .fulfilled(let output): resolve(output)
        case .rejected(let error): reject(error)
        }
    }
    private func fullfill(_ output: Output) {
        self.state = .fulfilled(output)
        for subscriber in subscribers { subscriber.resolve(output) }
        self.subscribers = []
    }
    private func reject(_ error: Failure) {
        self.state = .rejected(error)
        for subscriber in subscribers { subscriber.reject(error) }
        self.subscribers = []
    }
}

extension Promise {
    public static func resolve(_ output: Output) -> Promise<Output, Never> where Failure == Never {
        Promise<Output, Never> { resolve, _ in resolve(output) }
    }

    public static func resolve(_ handler: (@escaping (Output)->())->()) -> Promise<Output, Never> where Failure == Never {
        Promise<Output, Never> { resolve, _ in handler(resolve) }
    }

    public static func reject(_ error: Failure) -> Promise<Void, Failure> where Output == Void {
        Promise<Void, Failure> { _, reject in reject(error) }
    }

    public static func reject(_ handler: (@escaping (Failure)->())->()) -> Promise<Void, Failure> where Output == Void {
        Promise<Void, Failure> { _, reject in handler(reject) }
    }
}

extension Promise {
    public func map<T>(_ tranceform: @escaping (Output)->T) -> Promise<T, Failure> {
        Promise<T, Failure> { resolve, reject in
            self.subscribe({ resolve(tranceform($0)) }, reject)
        }
    }

    public func flatMap<T>(_ tranceform: @escaping (Output)->Promise<T, Failure>) -> Promise<T, Failure> {
        Promise<T, Failure> { resolve, reject in
            self.subscribe({ tranceform($0).sink(resolve, reject) }, reject)
        }
    }

    public func mapError<T>(_ tranceform: @escaping (Failure)->T) -> Promise<Output, T> {
        Promise<Output, T> { resolve, reject in
            self.subscribe(resolve, { reject(tranceform($0)) })
        }
    }

    public func replaceError(_ tranceform: @escaping (Failure)->Output) -> Promise<Output, Never> {
        Promise<Output, Never> { resolve, _ in
            self.subscribe(resolve, { resolve(tranceform($0)) })
        }
    }

    public func `catch`(_ onRejected: @escaping (Error)->()) -> Promise<Void, Never> {
        Promise<Void, Never> { resolve, _ in
            self.subscribe({_ in resolve(()) }, onRejected)
        }
    }

    public func sink(_ onFulfilled: @escaping (Output)->(), _ onRejected: @escaping (Failure)->()) {
        self.subscribe(onFulfilled, onRejected)
    }

    public func sink(_ onFulfilled: @escaping (Output)->()) where Failure == Never {
        self.subscribe(onFulfilled, {_ in})
    }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1