Flutter 的异步问题,为什么和前端 Promise 问题高度相似?


网罗开发 (小红书、快手、视频号同名)

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验 。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员

👋 大家好,我是展菲!

📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。

📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。

💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。

📅 最新动态:2025 年 3 月 17 日

快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!

文章目录

前言

最近在做一个 Flutter 项目的时候,遇到了一个经典的异步问题:页面销毁后还在调用 setState,导致应用崩溃。这个问题让我想起了前端开发中经常遇到的 Promise race condition,还有 iOS 开发中的异步任务管理。仔细一想,这三个平台的异步问题其实高度相似,都是异步操作和生命周期管理之间的矛盾。

今天我们就来聊聊这三个平台的异步问题,看看它们为什么这么相似,以及如何用一套通用的防御模式来解决这些问题。

异步和生命周期的通病

不管是 Flutter、前端还是 iOS,异步操作都有一个共同的特点:它们不会立即完成,而是在未来的某个时间点才返回结果。这就带来了一个问题:当异步操作还在进行中的时候,用户可能已经离开了当前页面,或者组件已经被销毁了。

Flutter 中的典型场景

在 Flutter 中,最常见的场景是这样的:

dart 复制代码
class UserProfilePage extends StatefulWidget {
  @override
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  String? userName;
  
  @override
  void initState() {
    super.initState();
    // 发起异步请求获取用户信息
    fetchUserInfo();
  }
  
  Future<void> fetchUserInfo() async {
    // 模拟网络请求
    await Future.delayed(Duration(seconds: 2));
    
    // 问题来了:如果用户在这 2 秒内离开了页面,这里调用 setState 就会报错
    setState(() {
      userName = "张三";
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("用户信息")),
      body: Center(
        child: Text(userName ?? "加载中..."),
      ),
    );
  }
}

这段代码看起来没什么问题,但实际上有一个严重的 bug:如果用户在 fetchUserInfo 还没完成的时候就离开了页面,setState 就会在已经销毁的 State 对象上调用,导致应用崩溃。

前端 React 中的类似问题

在前端开发中,这个问题同样存在:

javascript 复制代码
import React, { useState, useEffect } from 'react';

function UserProfile() {
  const [userName, setUserName] = useState(null);
  
  useEffect(() => {
    // 发起异步请求
    fetchUserInfo();
  }, []);
  
  async function fetchUserInfo() {
    // 模拟网络请求
    await new Promise(resolve => setTimeout(resolve, 2000));
    
    // 问题:如果组件已经卸载,这里调用 setState 就会报错
    setUserName("张三");
  }
  
  return (
    <div>
      <h1>{userName || "加载中..."}</h1>
    </div>
  );
}

React 会警告你:"Can't perform a React state update on an unmounted component",但不会直接崩溃。不过这个问题在 React Native 中会更严重,因为 RN 的错误处理机制和 Web 不太一样。

iOS 中的类似问题

在 iOS 开发中,这个问题同样存在,但表现形式略有不同:

swift 复制代码
class UserProfileViewController: UIViewController {
    var userNameLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        fetchUserInfo()
    }
    
    func fetchUserInfo() {
        // 发起异步请求
        Task {
            // 模拟网络请求
            try? await Task.sleep(for: .seconds(2))
            
            // 问题:如果视图控制器已经被销毁,这里更新 UI 可能会有问题
            userNameLabel.text = "张三"
        }
    }
}

虽然 Swift 的 async/await 机制相对更安全一些,但如果你不检查视图控制器的状态,同样可能出现问题。

Flutter 的 mounted 检查机制

Flutter 提供了一个 mounted 属性来检查 State 对象是否还在 widget 树中。这是 Flutter 的防御机制,但需要开发者手动检查。

mounted 属性的作用

mounted 是一个布尔值,表示当前的 State 对象是否还在 widget 树中。当 widget 被销毁时,mounted 会变成 false

dart 复制代码
class _UserProfilePageState extends State<UserProfilePage> {
  String? userName;
  
  @override
  void initState() {
    super.initState();
    fetchUserInfo();
  }
  
  Future<void> fetchUserInfo() async {
    await Future.delayed(Duration(seconds: 2));
    
    // 关键:在调用 setState 之前检查 mounted
    if (!mounted) {
      return; // 如果已经销毁,直接返回
    }
    
    setState(() {
      userName = "张三";
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("用户信息")),
      body: Center(
        child: Text(userName ?? "加载中..."),
      ),
    );
  }
}

这样就能避免在已销毁的 State 对象上调用 setState 了。

mounted 检查的最佳实践

在实际开发中,我们应该在每次异步操作完成后都检查 mounted

dart 复制代码
Future<void> fetchUserInfo() async {
  try {
    // 第一步:发起网络请求
    final response = await http.get(Uri.parse('https://api.example.com/user'));
    
    // 检查 mounted
    if (!mounted) return;
    
    // 第二步:解析数据
    final data = json.decode(response.body);
    
    // 再次检查 mounted(因为解析可能也需要时间)
    if (!mounted) return;
    
    // 第三步:更新状态
    setState(() {
      userName = data['name'];
    });
  } catch (e) {
    // 错误处理也要检查 mounted
    if (!mounted) return;
    
    setState(() {
      // 显示错误信息
    });
  }
}

mounted 的局限性

虽然 mounted 能解决大部分问题,但它也有一些局限性:

  1. 需要手动检查:每次异步操作后都要记得检查,容易遗漏
  2. 不能防止所有问题 :如果异步操作中还有其他副作用(比如更新全局状态、发送日志等),mounted 检查也帮不上忙
  3. 代码冗余 :每个异步方法都要写 if (!mounted) return;,代码会变得很冗长

React Native 的 isMounted 检查

React Native 也有类似的机制,但历史上有一些变化。

React Native 的 isMounted 问题

在早期的 React Native 版本中,有一个 isMounted 方法,但后来被移除了,因为 React 团队认为这个方法容易被误用。不过在实际开发中,我们仍然需要类似的检查机制。

javascript 复制代码
import React, { useState, useEffect, useRef } from 'react';

function UserProfile() {
  const [userName, setUserName] = useState(null);
  const isMountedRef = useRef(true);
  
  useEffect(() => {
    // 组件挂载时设置为 true
    isMountedRef.current = true;
    
    fetchUserInfo();
    
    // 组件卸载时设置为 false
    return () => {
      isMountedRef.current = false;
    };
  }, []);
  
  async function fetchUserInfo() {
    await new Promise(resolve => setTimeout(resolve, 2000));
    
    // 检查组件是否还在挂载状态
    if (!isMountedRef.current) {
      return;
    }
    
    setUserName("张三");
  }
  
  return (
    <div>
      <h1>{userName || "加载中..."}</h1>
    </div>
  );
}

React 18 的 StrictMode 双重渲染问题

在 React 18 中,还有一个新问题:StrictMode 会在开发环境下双重渲染组件,这会导致异步请求被执行两次。这虽然不是生命周期问题,但也和异步操作有关。

javascript 复制代码
useEffect(() => {
  let cancelled = false;
  
  async function fetchUserInfo() {
    const response = await fetch('/api/user');
    const data = await response.json();
    
    // 检查是否被取消
    if (!cancelled) {
      setUserName(data.name);
    }
  }
  
  fetchUserInfo();
  
  // 清理函数
  return () => {
    cancelled = true;
  };
}, []);

为什么 iOS 原生更安全

iOS 的异步机制相对来说更安全一些,主要有以下几个原因:

Swift 的结构化并发

Swift 5.5 引入的 async/await 和结构化并发机制,让异步代码更容易管理:

swift 复制代码
class UserProfileViewController: UIViewController {
    var userNameLabel: UILabel!
    private var fetchTask: Task<Void, Never>?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        fetchUserInfo()
    }
    
    func fetchUserInfo() {
        // 取消之前的任务(如果有)
        fetchTask?.cancel()
        
        // 创建新任务
        fetchTask = Task { @MainActor in
            do {
                // 模拟网络请求
                try await Task.sleep(for: .seconds(2))
                
                // 检查任务是否被取消
                try Task.checkCancellation()
                
                // 更新 UI(@MainActor 确保在主线程)
                userNameLabel.text = "张三"
            } catch {
                // 任务被取消或其他错误
                print("任务被取消或出错: \(error)")
            }
        }
    }
    
    deinit {
        // 视图控制器销毁时取消任务
        fetchTask?.cancel()
    }
}

Task 的取消机制

Swift 的 Task 提供了内置的取消机制,可以通过 Task.checkCancellation() 检查任务是否被取消:

swift 复制代码
func fetchUserInfo() async throws {
    // 第一步:发起网络请求
    let (data, _) = try await URLSession.shared.data(from: url)
    
    // 检查任务是否被取消
    try Task.checkCancellation()
    
    // 第二步:解析数据
    let user = try JSONDecoder().decode(User.self, from: data)
    
    // 再次检查任务是否被取消
    try Task.checkCancellation()
    
    // 第三步:更新 UI
    await MainActor.run {
        userNameLabel.text = user.name
    }
}

@MainActor 的线程安全保证

Swift 的 @MainActor 注解可以确保代码在主线程执行,这对于 UI 更新非常重要:

swift 复制代码
@MainActor
class UserProfileViewController: UIViewController {
    // 所有属性和方法都在主线程执行
    var userNameLabel: UILabel!
    
    func updateUI() {
        // 这里不需要手动切换到主线程
        userNameLabel.text = "张三"
    }
}

强类型系统的帮助

Swift 的强类型系统可以在编译时发现一些问题,比如类型不匹配、可选值未处理等,这可以减少运行时错误。

一套跨端通用的异步防御模式

虽然三个平台的机制不同,但我们可以总结出一套通用的防御模式,让异步代码更安全。

模式一:生命周期检查

在异步操作完成后,检查组件/视图是否还在生命周期内:

Flutter 版本:

dart 复制代码
Future<void> fetchUserInfo() async {
  await Future.delayed(Duration(seconds: 2));
  
  if (!mounted) return; // 生命周期检查
  
  setState(() {
    userName = "张三";
  });
}

React/React Native 版本:

javascript 复制代码
useEffect(() => {
  let isMounted = true;
  
  async function fetchUserInfo() {
    await new Promise(resolve => setTimeout(resolve, 2000));
    
    if (!isMounted) return; // 生命周期检查
    
    setUserName("张三");
  }
  
  fetchUserInfo();
  
  return () => {
    isMounted = false; // 清理
  };
}, []);

iOS 版本:

swift 复制代码
func fetchUserInfo() {
    fetchTask = Task { @MainActor in
        do {
            try await Task.sleep(for: .seconds(2))
            
            try Task.checkCancellation() // 任务取消检查
            
            userNameLabel.text = "张三"
        } catch {
            // 任务被取消
        }
    }
}

deinit {
    fetchTask?.cancel() // 清理
}

模式二:任务取消机制

在组件/视图销毁时,主动取消正在进行的异步任务:

Flutter 版本:

dart 复制代码
class _UserProfilePageState extends State<UserProfilePage> {
  Future<void>? _fetchTask;
  
  @override
  void initState() {
    super.initState();
    _fetchTask = fetchUserInfo();
  }
  
  Future<void> fetchUserInfo() async {
    await Future.delayed(Duration(seconds: 2));
    
    if (!mounted) return;
    
    setState(() {
      userName = "张三";
    });
  }
  
  @override
  void dispose() {
    // 取消任务(虽然 Future 不能直接取消,但可以通过检查 mounted 来模拟)
    _fetchTask = null;
    super.dispose();
  }
}

React/React Native 版本:

javascript 复制代码
useEffect(() => {
  const controller = new AbortController();
  
  async function fetchUserInfo() {
    try {
      const response = await fetch('/api/user', {
        signal: controller.signal // 使用 AbortSignal
      });
      const data = await response.json();
      setUserName(data.name);
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('请求被取消');
      }
    }
  }
  
  fetchUserInfo();
  
  return () => {
    controller.abort(); // 取消请求
  };
}, []);

iOS 版本:

swift 复制代码
private var fetchTask: Task<Void, Never>?

func fetchUserInfo() {
    fetchTask?.cancel() // 取消之前的任务
    fetchTask = Task {
        // 异步操作
    }
}

deinit {
    fetchTask?.cancel() // 清理
}

模式三:使用状态管理库

使用状态管理库(如 Provider、Riverpod、Redux 等)来管理异步状态,这些库通常内置了生命周期管理:

Flutter + Riverpod 版本:

dart 复制代码
final userProvider = FutureProvider<User>((ref) async {
  // Riverpod 会自动处理生命周期
  final response = await http.get(Uri.parse('https://api.example.com/user'));
  return User.fromJson(json.decode(response.body));
});

class UserProfilePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    
    return userAsync.when(
      data: (user) => Text(user.name),
      loading: () => CircularProgressIndicator(),
      error: (error, stack) => Text('错误: $error'),
    );
  }
}

React + React Query 版本:

javascript 复制代码
import { useQuery } from 'react-query';

function UserProfile() {
  // React Query 会自动处理生命周期和缓存
  const { data, isLoading, error } = useQuery('user', fetchUserInfo);
  
  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  
  return <div>{data.name}</div>;
}

模式四:封装通用工具函数

我们可以封装一些通用的工具函数,让异步操作更安全:

Flutter 版本:

dart 复制代码
extension SafeSetState on State {
  void safeSetState(VoidCallback fn) {
    if (mounted) {
      setState(fn);
    }
  }
}

// 使用
Future<void> fetchUserInfo() async {
  await Future.delayed(Duration(seconds: 2));
  
  safeSetState(() {
    userName = "张三";
  });
}

React 版本:

javascript 复制代码
function useSafeState(initialState) {
  const [state, setState] = useState(initialState);
  const isMountedRef = useRef(true);
  
  useEffect(() => {
    return () => {
      isMountedRef.current = false;
    };
  }, []);
  
  const safeSetState = useCallback((newState) => {
    if (isMountedRef.current) {
      setState(newState);
    }
  }, []);
  
  return [state, safeSetState];
}

// 使用
function UserProfile() {
  const [userName, setUserName] = useSafeState(null);
  
  useEffect(() => {
    fetchUserInfo().then(setUserName);
  }, []);
}

iOS 版本:

swift 复制代码
extension UIViewController {
    func safeAsyncUpdate<T>(_ task: Task<T, Error>, update: @escaping (T) -> Void) {
        Task { @MainActor in
            do {
                let result = try await task.value
                if !isViewLoaded || view.window == nil {
                    return // 视图未加载或不在窗口中
                }
                update(result)
            } catch {
                // 处理错误
            }
        }
    }
}

// 使用
func fetchUserInfo() {
    let task = Task {
        // 异步操作
        return "张三"
    }
    
    safeAsyncUpdate(task) { name in
        userNameLabel.text = name
    }
}

实际场景中的应用

让我们看看这些防御模式在实际项目中的应用。

场景一:列表页面的数据加载

在列表页面中,用户可能会快速滚动,导致多个异步请求同时进行。我们需要确保只有最新的请求结果才会更新 UI。

Flutter 版本:

dart 复制代码
class ProductListPage extends StatefulWidget {
  @override
  _ProductListPageState createState() => _ProductListPageState();
}

class _ProductListPageState extends State<ProductListPage> {
  List<Product> products = [];
  int currentPage = 1;
  bool isLoading = false;
  Future<void>? _loadTask;
  
  @override
  void initState() {
    super.initState();
    loadProducts();
  }
  
  Future<void> loadProducts() async {
    if (isLoading) return;
    
    setState(() {
      isLoading = true;
    });
    
    _loadTask = _fetchProducts();
    await _loadTask;
    
    if (!mounted) return;
    
    setState(() {
      isLoading = false;
    });
  }
  
  Future<void> _fetchProducts() async {
    // 模拟网络请求
    await Future.delayed(Duration(seconds: 1));
    
    if (!mounted) return;
    
    final newProducts = List.generate(10, (i) => Product(id: i));
    
    if (!mounted) return;
    
    setState(() {
      products.addAll(newProducts);
      currentPage++;
    });
  }
  
  @override
  void dispose() {
    _loadTask = null;
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text(products[index].name));
      },
    );
  }
}

React 版本:

javascript 复制代码
function ProductList() {
  const [products, setProducts] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false);
  const abortControllerRef = useRef(null);
  
  useEffect(() => {
    loadProducts();
    
    return () => {
      // 清理:取消正在进行的请求
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
    };
  }, []);
  
  async function loadProducts() {
    if (isLoading) return;
    
    setIsLoading(true);
    
    // 取消之前的请求
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    
    // 创建新的 AbortController
    abortControllerRef.current = new AbortController();
    
    try {
      const response = await fetch(`/api/products?page=${currentPage}`, {
        signal: abortControllerRef.current.signal
      });
      
      const data = await response.json();
      
      setProducts(prev => [...prev, ...data.products]);
      setCurrentPage(prev => prev + 1);
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('加载失败:', error);
      }
    } finally {
      setIsLoading(false);
    }
  }
  
  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
}

场景二:搜索功能的防抖处理

在搜索功能中,用户输入时会触发多个搜索请求,我们需要确保只处理最新的搜索结果。

Flutter 版本:

dart 复制代码
class SearchPage extends StatefulWidget {
  @override
  _SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  String _searchQuery = '';
  List<String> _searchResults = [];
  Timer? _debounceTimer;
  Future<void>? _searchTask;
  
  void onSearchChanged(String query) {
    _searchQuery = query;
    
    // 取消之前的防抖定时器
    _debounceTimer?.cancel();
    
    // 设置新的防抖定时器
    _debounceTimer = Timer(Duration(milliseconds: 500), () {
      performSearch(query);
    });
  }
  
  Future<void> performSearch(String query) async {
    if (query.isEmpty) {
      setState(() {
        _searchResults = [];
      });
      return;
    }
    
    // 取消之前的搜索任务
    _searchTask = null;
    
    _searchTask = _fetchSearchResults(query);
    await _searchTask;
  }
  
  Future<void> _fetchSearchResults(String query) async {
    // 模拟网络请求
    await Future.delayed(Duration(seconds: 1));
    
    if (!mounted) return;
    
    // 检查查询是否还是最新的
    if (_searchQuery != query) return;
    
    final results = ['结果1', '结果2', '结果3'];
    
    if (!mounted) return;
    
    setState(() {
      _searchResults = results;
    });
  }
  
  @override
  void dispose() {
    _debounceTimer?.cancel();
    _searchTask = null;
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          TextField(
            onChanged: onSearchChanged,
            decoration: InputDecoration(hintText: "搜索"),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _searchResults.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(_searchResults[index]));
              },
            ),
          ),
        ],
      ),
    );
  }
}

React 版本:

javascript 复制代码
function SearchPage() {
  const [searchQuery, setSearchQuery] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const abortControllerRef = useRef(null);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      performSearch(searchQuery);
    }, 500);
    
    return () => {
      clearTimeout(timer);
    };
  }, [searchQuery]);
  
  async function performSearch(query) {
    if (!query) {
      setSearchResults([]);
      return;
    }
    
    // 取消之前的请求
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    
    abortControllerRef.current = new AbortController();
    
    try {
      const response = await fetch(`/api/search?q=${query}`, {
        signal: abortControllerRef.current.signal
      });
      
      const data = await response.json();
      
      // 检查查询是否还是最新的
      if (abortControllerRef.current.signal.aborted) return;
      
      setSearchResults(data.results);
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('搜索失败:', error);
      }
    }
  }
  
  return (
    <div>
      <input
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="搜索"
      />
      <ul>
        {searchResults.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
}

总结

Flutter、前端和 iOS 的异步问题确实高度相似,核心都是异步操作和生命周期管理之间的矛盾。虽然每个平台的解决方案略有不同,但我们可以总结出一套通用的防御模式:

  1. 生命周期检查:在异步操作完成后,检查组件/视图是否还在生命周期内
  2. 任务取消机制:在组件/视图销毁时,主动取消正在进行的异步任务
  3. 使用状态管理库:利用成熟的状态管理库,它们通常内置了生命周期管理
  4. 封装通用工具函数:创建一些通用的工具函数,让异步操作更安全

在实际开发中,我们应该根据项目的具体情况选择合适的方案。对于简单的场景,手动检查生命周期就足够了;对于复杂的场景,使用状态管理库会更省心。

最重要的是,我们要时刻记住:异步操作是有延迟的,在异步操作完成之前,用户可能已经离开了当前页面。所以,每次异步操作完成后,都要检查一下组件/视图是否还在生命周期内,这样才能避免崩溃和内存泄漏。

希望这篇文章能帮助你更好地理解跨平台的异步问题,写出更安全的异步代码!

完整可运行 Demo 代码

下面是一个完整的 Flutter Demo,展示了如何正确处理异步操作和生命周期:

dart 复制代码
import 'package:flutter/material.dart';
import 'dart:async';

// 工具类:安全的 setState 扩展
extension SafeSetState on State {
  void safeSetState(VoidCallback fn) {
    if (mounted) {
      setState(fn);
    }
  }
}

// 用户信息模型
class User {
  final String name;
  final String email;
  
  User({required this.name, required this.email});
}

// 用户信息页面
class UserProfilePage extends StatefulWidget {
  @override
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  User? user;
  bool isLoading = false;
  String? errorMessage;
  Future<void>? _fetchTask;
  
  @override
  void initState() {
    super.initState();
    fetchUserInfo();
  }
  
  Future<void> fetchUserInfo() async {
    safeSetState(() {
      isLoading = true;
      errorMessage = null;
    });
    
    _fetchTask = _performFetch();
    await _fetchTask;
  }
  
  Future<void> _performFetch() async {
    try {
      // 模拟网络请求延迟
      await Future.delayed(Duration(seconds: 2));
      
      // 检查生命周期
      if (!mounted) return;
      
      // 模拟数据解析
      await Future.delayed(Duration(milliseconds: 500));
      
      // 再次检查生命周期
      if (!mounted) return;
      
      // 模拟获取到的用户数据
      final fetchedUser = User(
        name: "张三",
        email: "zhangsan@example.com",
      );
      
      // 更新 UI
      safeSetState(() {
        user = fetchedUser;
        isLoading = false;
      });
    } catch (e) {
      // 错误处理也要检查生命周期
      if (!mounted) return;
      
      safeSetState(() {
        errorMessage = "加载失败: $e";
        isLoading = false;
      });
    }
  }
  
  @override
  void dispose() {
    // 清理:虽然 Future 不能直接取消,但可以通过检查 mounted 来模拟
    _fetchTask = null;
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("用户信息"),
      ),
      body: Center(
        child: _buildContent(),
      ),
    );
  }
  
  Widget _buildContent() {
    if (isLoading) {
      return CircularProgressIndicator();
    }
    
    if (errorMessage != null) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            errorMessage!,
            style: TextStyle(color: Colors.red),
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: fetchUserInfo,
            child: Text("重试"),
          ),
        ],
      );
    }
    
    if (user == null) {
      return Text("暂无数据");
    }
    
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          "姓名: ${user!.name}",
          style: TextStyle(fontSize: 20),
        ),
        SizedBox(height: 16),
        Text(
          "邮箱: ${user!.email}",
          style: TextStyle(fontSize: 16),
        ),
      ],
    );
  }
}

// 搜索页面示例
class SearchPage extends StatefulWidget {
  @override
  _SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  String _searchQuery = '';
  List<String> _searchResults = [];
  Timer? _debounceTimer;
  Future<void>? _searchTask;
  
  void onSearchChanged(String query) {
    setState(() {
      _searchQuery = query;
    });
    
    // 取消之前的防抖定时器
    _debounceTimer?.cancel();
    
    // 设置新的防抖定时器
    _debounceTimer = Timer(Duration(milliseconds: 500), () {
      performSearch(query);
    });
  }
  
  Future<void> performSearch(String query) async {
    if (query.isEmpty) {
      safeSetState(() {
        _searchResults = [];
      });
      return;
    }
    
    _searchTask = _fetchSearchResults(query);
    await _searchTask;
  }
  
  Future<void> _fetchSearchResults(String query) async {
    try {
      // 模拟网络请求
      await Future.delayed(Duration(seconds: 1));
      
      // 检查生命周期
      if (!mounted) return;
      
      // 检查查询是否还是最新的
      if (_searchQuery != query) return;
      
      // 模拟搜索结果
      final results = List.generate(
        5,
        (index) => "搜索结果 ${index + 1} for '$query'",
      );
      
      // 更新 UI
      safeSetState(() {
        _searchResults = results;
      });
    } catch (e) {
      if (!mounted) return;
      
      safeSetState(() {
        _searchResults = [];
      });
    }
  }
  
  @override
  void dispose() {
    _debounceTimer?.cancel();
    _searchTask = null;
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("搜索"),
      ),
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(16),
            child: TextField(
              onChanged: onSearchChanged,
              decoration: InputDecoration(
                hintText: "输入搜索关键词",
                border: OutlineInputBorder(),
              ),
            ),
          ),
          Expanded(
            child: _searchResults.isEmpty
                ? Center(
                    child: Text(
                      _searchQuery.isEmpty
                          ? "请输入搜索关键词"
                          : "搜索中...",
                    ),
                  )
                : ListView.builder(
                    itemCount: _searchResults.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(_searchResults[index]),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

// 主应用
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '异步防御模式 Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("异步防御模式 Demo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => UserProfilePage(),
                  ),
                );
              },
              child: Text("用户信息页面"),
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => SearchPage(),
                  ),
                );
              },
              child: Text("搜索页面"),
            ),
          ],
        ),
      ),
    );
  }
}

这个 Demo 展示了:

  1. 安全的 setState 扩展 :通过 safeSetState 方法,自动检查 mounted 状态
  2. 用户信息页面:展示了如何在异步操作中正确检查生命周期
  3. 搜索页面:展示了防抖处理和生命周期检查的结合使用
  4. 任务管理 :通过 _fetchTask_searchTask 来跟踪异步任务

你可以运行这个 Demo,快速切换页面来测试异步操作的防御机制是否正常工作。

相关推荐
程序员Agions2 小时前
AI 编程的"效率幻觉":为什么用了 Cursor 之后,你反而更累了?
前端·ai编程
Android技术之家2 小时前
在手机上跑大模型?Google AI Edge Gallery 开源项目深度解析
前端·人工智能·edge·开源
DEMO派2 小时前
CSS优先级规则以及如何提升优先级方案详解
前端·javascript·css·vue.js·reactjs·html5·angular.js
摘星编程2 小时前
Flutter for OpenHarmony 实战:AlertDialog 警告对话框详解
flutter
hhcccchh2 小时前
学习vue第十一天 Vue3组件化开发指南:搭积木的艺术
前端·vue.js·学习
AntoineGriezmann2 小时前
基于 Unocss 的后台系统 SVG 图标方案实践
前端
小夏卷编程2 小时前
ant-design-vue 2.0 a-table 中实现特殊行样式,选中样式,鼠标悬浮样式不一样
前端·javascript·vue.js
wulijuan8886662 小时前
前端性能优化之图片webp
前端
一颗烂土豆2 小时前
ECharts 水球图不够炫?试试 RayChart 的创意可视化玩法
前端·vue.js·数据可视化