---
# System prepended metadata

title: '`@Async` leads to null'
tags: [chatgpt]

---

# `@Async` leads to null

[TOC]

###### tags: `chatgpt`

---

## TL;DR

> Controller:
> ```
> @GetMapping
> public List<InAppMail> index(@RequestParam(defaultValue = "1") final int p, @RequestParam(defaultValue = "10") final int s) {
> 	return mailService.load(
> 		p < 1 ? 0 : p - 1,
> 		s
> 	);
> }
> ```
>
> Service:
> ```
> @Async
> @Transactional(readOnly = true)
> public List<InAppMail> load(final int p, final int s) {
> 	return inAppMailRepository.
> 		findAll(
> 			PageRequest.of(
> 				p,
> 				s
> 			)
> 		).
> 		getContent();
> }
> ```
>
> 為何 service method 有 `@Async` 時會回傳 `null`，然而沒有 `@Async` 時不會

當在 Spring Service 方法上添加 `@Async` 注解時，它會使該方法的執行在另一個線程中，而不是在當前線程中執行。當控制器調用 Service 方法時，Service 方法可能仍在運行，當控制器返回結果時，Service 方法還沒有完成。因此，Service 方法將不會返回任何結果，並且控制器將接收到一個 `null` 結果。

解決這個問題的方法是讓控制器等待 Service 方法完成。一種方法是將 Service 方法返回的 `Future` 對象傳遞給控制器，然後使用 `Future` 對象的 `get` 方法等待它的完成。另一種方法是使用 Spring 的 `DeferredResult` 或 `CompletableFuture` 來處理異步返回值，以便控制器能夠在 Service 方法完成後繼續處理返回值。

如果 Service 方法不是異步的，它將在控制器調用它時立即運行，並且在返回結果之前完成。這意味著控制器將收到正確的返回值，而不是 `null`。

## 分別用 `Future` 跟 `CompletableFuture` 重寫以上的代碼

使用 `Future` 重寫以上代碼可以這樣實現：

Controller：

```java
@GetMapping
public List<InAppMail> index(@RequestParam(defaultValue = "1") final int p, @RequestParam(defaultValue = "10") final int s) throws InterruptedException, ExecutionException {
	Future<List<InAppMail>> future = mailService.load(p < 1 ? 0 : p - 1, s);
	// 等待 Service 方法執行完畢，獲取返回值
	return future.get();
}
```

Service：

```java
@Transactional(readOnly = true)
public Future<List<InAppMail>> load(final int p, final int s) {
	// 使用 Java 的 Executors 工具類創建一個新的線程執行該方法
	ExecutorService executor = Executors.newSingleThreadExecutor();
	Future<List<InAppMail>> future = executor.submit(() -> {
		return inAppMailRepository.findAll(PageRequest.of(p, s)).getContent();
	});
	executor.shutdown();
	return future;
}
```

使用 `CompletableFuture` 重寫以上代碼可以這樣實現：

Controller：

```java
@GetMapping
public CompletableFuture<List<InAppMail>> index(@RequestParam(defaultValue = "1") final int p, @RequestParam(defaultValue = "10") final int s) {
	return mailService.load(p < 1 ? 0 : p - 1, s);
}
```

Service：

```java
@Transactional(readOnly = true)
public CompletableFuture<List<InAppMail>> load(final int p, final int s) {
	return CompletableFuture.supplyAsync(() -> {
		return inAppMailRepository.findAll(PageRequest.of(p, s)).getContent();
	});
}
```

使用 `CompletableFuture` 可以更簡單地實現異步返回值，它的接口更加簡潔，而且可以更加自由地使用 lambda 表達式實現異步任務。此外，`CompletableFuture` 還提供了更多的方法來處理異步返回值，例如使用 `thenApply` 方法處理結果，使用 `exceptionally` 方法處理異常，以及使用 `whenComplete` 方法處理完成事件等。

## 舉個例子實現 `CompletableFuture.completeAsync(Supplier<? extends T> supplier)` 的用法

`CompletableFuture.completeAsync(Supplier<? extends T> supplier)` 方法可以用來異步執行一個 `Supplier` 並將結果作為 `CompletableFuture` 的返回值。

以下是一個使用 `completeAsync(Supplier)` 方法的示例，該示例使用異步方式從 API 獲取一個用戶名單，並將其轉換為用戶對象列表：

```java
public class UserService {

	private final RestTemplate restTemplate;

	public UserService(RestTemplate restTemplate) {
		this.restTemplate = restTemplate;
	}

	public CompletableFuture<List<User>> getUsersAsync() {
		return CompletableFuture.supplyAsync(() -> {
			// 異步調用API，獲取用戶名單
			ResponseEntity<List<String>> response = restTemplate.exchange(
				"https://api.example.com/users",
				HttpMethod.GET,
				null,
				new ParameterizedTypeReference<List<String>>() {}
			);
			List<String> usernames = response.getBody();

			// 將用戶名單轉換為用戶對象列表
			List<User> users = usernames.stream().map(username -> {
				// 異步調用API，獲取用戶詳細信息
				CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> {
					ResponseEntity<User> userResponse = restTemplate.getForEntity(
						"https://api.example.com/users/" + username,
						User.class
					);
					return userResponse.getBody();
				});
				try {
					// 等待用戶詳細信息獲取完畢
					return userFuture.get();
				} catch (InterruptedException | ExecutionException e) {
					throw new RuntimeException(e);
				}
			}).collect(Collectors.toList());

			return users;
		});
	}
}
```

在這個示例中，`getUsersAsync()` 方法返回一個 `CompletableFuture<List<User>>` 對象。它使用 `supplyAsync(Supplier)` 方法異步獲取用戶名單，並將結果轉換為用戶對象列表。

在轉換用戶名單為用戶對象列表時，它使用了另一個異步操作。它使用了 `supplyAsync(Supplier)` 方法異步獲取用戶詳細信息，並使用 `get()` 方法等待該操作完成。由於 `get()` 方法會阻塞當前線程，所以它需要在另一個線程中執行，這就是為什麼它使用了另一個異步操作的原因。

這個示例演示了如何使用 `CompletableFuture.completeAsync(Supplier)` 方法異步執行任務，並在該任務完成時獲取返回值。
