SomeAsyncSwift 1.0.0

SomeAsyncSwift 1.0.0

Sergey Makeev 维护。



  • 作者:
  • Sergey Makeev

AsyncSwift

使用 futures 和 async-await 语法在 Swift 中执行异步任务的工具包。

如何使用

  1. 您可以直接获取源代码。
  2. 使用 Cocoapods
    pod "SomeAsyncSwift"	
    
    并在您的 Swift 文件中
    import SomeAsyncSwift

异步编程:futures, async, await

异步操作允许程序在等待其他操作完成时继续完成工作。以下是一些常见的异步操作:

  • 通过网络获取数据。
  • 向数据库写入。
  • 从文件中读取数据。

AsyncSwift 允许您使用 AsyncAwaitFuture 类和 async 以及 await 关键字。

为了简单起见,AsyncAwaitFuture 的名称被简化为 AFuture

AFuture

AFuture 是一个带有一个泛型类型 result 参数的泛型类。AFuture 可以称为 SomeFuture

未来是具有特定结果类型的 AFuture 类的实例。未来代表异步操作的结果,并具有两种状态:resolved == trueresolved == false。此外,由于未来不仅可能成功解决也可能出错,它还提供了 hasError 子状态。

如果未来已解决,则可以提供结果。result 属性是一个只读的泛型类型属性,它可以空值。未来可以以 nil 结果解决。这并不意味着有什么错误。因此,要确定未来是否已解决,您应检查 resolved 属性。在出现错误的情况下,resolved 属性显示 true,但 hasError 属性也显示 true。具体错误可以在 Error 属性中找到。它具有 Any? 类型,也可以是空值。

例子

    let future1 = SomeFuture.delay(3)

这里 future1 是一个 AsyncAwaitFuture,它在 3 秒后将解决。

	let future2: AFuture<Int> = async {
  	for _ in 1...1000000 {
  		//do something
  	}
  	return 10 //resolve with 10
  }

async 特殊构造函数返回一个未来。将在稍后详细介绍。

这里 future2 是一个 AsyncAwaitFuture,在循环结束后它将用 10 解决。

  let future3 = SomeFuture.wait([future1, future2])

这里 future3 是一个 AsyncAwaitFuture,将在 future1future2 解决后解决。

要等待 future1future2,您可以使用 await 关键字,这在这个实现中只是函数。

try! await(SomeFuture.wait([future1, future2]))

在这种情况下,你的当前线程将等待 future1future2

与未来一起工作:异步和等待

asyncawait 关键字提供了一种声明式的方法来定义异步函数和获取其结果。

如果函数有声明返回类型,则将类型更新为 AFuture<T>,其中 T 是函数返回的价值的类型。如果函数没有显式返回值,则返回类型为 AFuture<void>SomeFuture。函数应该在体内部使用 async 关键字。

func f0() -> AFuture<Int> {
  async {
  	sleep(2) //to imitate some work in BG.
  	return 1
  }
}

此函数可以具有参数

  func f1(_ a: Int) -> AFuture<Int> {
  	async {
  	sleep(2)
  	return a
     }
  }

对于此类函数,您可以直接使用 await 以使您的代码等待解决未来。

  let result = try! await(f1(5))

您可以使用函数来获取未来并观察其状态。为了观察未来状态,您可以使用几种方法

onSuccess 方法。在未来解决后将被调用。如果调用已解决的未来的 onSuccess,它将被立即调用。

 future.onResolved { result in
	print(" Future resolved with:\(result)")
}

then 类似于 onSuccess,但返回等待第一个未来解决的未来。这允许您创建未来链。然后有一个类型参数,它将被包含到其未来中。

let future = findEntryPoint().then(EntryPoint.self) { entryPoint in
	guard let validEntryPoint = entryPoint else { fatalError()}
	return self.runFromEnryPoint(validEntryPoint)
}.then(Void.self, finishChainWithOptinalParameter)

在这个示例中,所有函数都返回未来对象,因此都是异步函数。

always类似于 onSuccess,但它也适用于错误情况。

最后一个示例可以用 async await 构造函数重写

 	  let entryPoint = await(findEntryPoint())
		guard let validEntryPoint = entryPoint else { fatalError() /*to test exceptions*/}
		let result = await(self.runFromEnryPoint(validEntryPoint))

如果您不希望线程在 await 中等待,请确保在 async 中调用它。async 将返回您可以订阅的未来。

注意:future 不会保留对自己引用的引用,这意味着在 futures 完成其工作之前,您始终需要保留对future的引用。

await

await 是一个使当前线程等待未来被解决的功能。如果 future 以错误方式解决,则抛出 AfutureErrors.FutureError。

在未返回未来功能的情况下使用时,您可以使用接受参数和函数的 await 变体。它接受 0 到 10 个参数和一个不返回 AFuture 的函数,并产生一个 AFuture<Type>,其中 Type 是您的函数返回类型。

  • 如果需要超过 10 个参数,则需要自己创建 await

错误处理

如何生成错误

如果异步函数抛出异常,future 可能会产生一个错误。

let future: AFuture<Int> = async {
	for i in 1...1000000 {
		if i == 10000 {
			throw(TestErrors.testExcepton(howMany: i))
		}
	}
	return 10
}

在这个示例中,future 不会返回 10,而是会发生类型为 TestErrors 的错误。以下是错误类型。

	enum TestErrors: Error {
		case testExcepton(howMany: Int)
	}

如何在 future 中处理错误

await 如果在 future 错误的情况下抛出异常。因此,第一种方式是常规异常处理。

但您可以使用 try!Future.wait 之前,因为它永远不会抛出。

请看这个例子

func testException() {
	let future: AFuture<Int> = async {
		for i in 1...1000000 {
			if i == 10000 {
				throw(TestErrors.testExcepton(howMany: i))
			}
		}
		return 10
	}
	
	try! await(SomeFuture.wait([future]))
	XCTAssert(future.resolved)
	XCTAssert(future.hasError)
	let error = future.error
	if let validError = error as? TestErrors {
		switch validError {
		case .testExcepton(let howMany): XCTAssert(howMany == 10000)
		}
	} else {
		XCTAssert(false)
	}
}

所以如您所见,SomeFuture.wait([future]) 的工作方式就好像 future 已经使用值解决。但将会有一个错误。

您还可以使用 onError 处理器。它的工作方式与 onSuccess 相同,但在出错的情况下。

如果您想要修复问题,请使用 catchError

future 连锁的示例

func testChain() {
	var gotThenBlock = false
	var wasInThen    = false
	var wasInAlways  = false
	let future: AFuture<String> = async { () -> Int in
		for i in 1...1000000 {
			if i == 10000 {
				throw(TestErrors.testExcepton(howMany: i))
			}
		}
		return 10
	}.then(String.self) { result in
		gotThenBlock = true
		return async {
			wasInThen = true
			return "Ten"
		}
	}.catchError { _ in
		return "10000"
	}.always {
		wasInAlways = true
	}

	let result = await(future)
	XCTAssert(result == "10000")
	XCTAssert(wasInAlways)
	XCTAssert(!gotThenBlock)
	XCTAssert(!wasInThen)
}

在这个测试中,第一个 future 提供 Int 结果,下一个 future 提供 String,但第一个 future 提供了错误。我们通过 catchError 捕获它,并在此提供修复的值。注意:catchError 是一个新的 future。这意味着它也可以有 then 并且可以提供错误。因此,您可以连续有几个 catchError

Always 在任何情况下都会被调用。

处理 await 中的错误

第一种选择是使用标准的 try-catch,因为如果 Future 出错,await 会抛出异常。

由于 await 接收 AFuture,您可以生成给 future 的 catchError。如果 catchError 总是产生值,您可以直接使用 try!。这不是推荐的方式,因为 catchError 也可能返回错误。这里这样做是为了简便。

func testExceptionsInAwait() {
	let future: AFuture<Int> = async {
		for i in 1...1000000 {
			if i == 10000 {
				throw(TestErrors.testExcepton(howMany: i))
			}
		}
		return 10
	}
	let result = try! await(future.catchError { error in
		if let validError = error as? TestErrors {
			switch validError {
			case .testExcepton(let howMany): XCTAssert(howMany == 10000)
			}
		} else {
			XCTAssert(false)
		}
		
		return 20
	})
	
	XCTAssert(result == 20)
}

如果您使用 await 到不提供 future 的函数,您就没有这样的机会,但可以使用第一种方式,即 try-catch

流和异步可观察属性

请查看 AsyncObservablesTestsStreamTests 以获取示例。