最近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
}
またfullfill
とreject
をそれぞれできるようにしておきます。
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})
}
}