魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - 實作 Promise 超時控制

學習如何實作 Promise 超時控制機制,掌握非同步任務的時間管理技巧,提升前端應用穩定性與面試競爭力。

影片縮圖

懶得看文章?那就來看影片吧

Promise 超時控制概述

在實際開發中,我們經常需要為非同步操作設定超時限制,避免長時間等待或無限阻塞。Promise 超時控制是一種重要的技術,可以確保應用程式的響應性和穩定性。

超時控制的重要性

  • 1. 防止無限等待:避免 Promise 永遠處於 pending 狀態
  • 2. 提升用戶體驗:及時響應用戶操作,避免界面凍結
  • 3. 資源管理:防止資源洩漏和系統過載
  • 4. 錯誤處理:提供明確的超時錯誤信息

在前端面試中,實作 Promise 超時控制是測試候選人對非同步處理、錯誤處理和時間管理理解的重要題目。

基本實作:Promise 超時控制

以下是一個基本的 Promise 超時控制實作:

// 基本 Promise 超時控制函數
function timeoutPromise(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error(`操作超時 (${timeout}ms)`));
      }, timeout);
    })
  ]);
}

// 使用示例
const fetchWithTimeout = (url, timeout = 5000) => {
  return timeoutPromise(
    fetch(url),
    timeout
  );
};

// 測試超時控制
fetchWithTimeout('https://api.example.com/data', 3000)
  .then(response => response.json())
  .then(data => {
    console.log('數據獲取成功:', data);
  })
  .catch(error => {
    if (error.message.includes('超時')) {
      console.error('請求超時,請稍後重試');
    } else {
      console.error('請求失敗:', error);
    }
  });

進階實作:可取消的 Promise

除了超時控制,我們還可以實作可取消的 Promise,提供更靈活的非同步操作控制:

// 可取消的 Promise 實作
function cancellablePromise(promise) {
  let isCancelled = false;
  
  const wrappedPromise = new Promise((resolve, reject) => {
    promise
      .then(result => {
        if (!isCancelled) {
          resolve(result);
        }
      })
      .catch(error => {
        if (!isCancelled) {
          reject(error);
        }
      });
  });
  
  // 返回 Promise 和取消函數
  return {
    promise: wrappedPromise,
    cancel: () => {
      isCancelled = true;
    }
  };
}

// 結合超時和取消功能
function timeoutCancellablePromise(promise, timeout) {
  const { promise: wrappedPromise, cancel } = cancellablePromise(promise);
  
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      cancel();
      reject(new Error(`操作超時 (${timeout}ms)`));
    }, timeout);
  });
  
  return Promise.race([wrappedPromise, timeoutPromise]);
}

// 使用示例
const longRunningTask = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('任務完成');
    }, 10000); // 10秒
  });
};

// 設定 5 秒超時
timeoutCancellablePromise(longRunningTask(), 5000)
  .then(result => {
    console.log('任務成功:', result);
  })
  .catch(error => {
    console.error('任務失敗:', error.message);
  });

實際應用場景

Promise 超時控制在實際開發中有很多應用場景:

1. API 請求超時控制

// API 請求超時控制
class ApiClient {
  constructor(baseURL, defaultTimeout = 10000) {
    this.baseURL = baseURL;
    this.defaultTimeout = defaultTimeout;
  }
  
  async request(endpoint, options = {}) {
    const { timeout = this.defaultTimeout, ...fetchOptions } = options;
    
    const url = `${this.baseURL}${endpoint}`;
    
    try {
      const response = await timeoutPromise(
        fetch(url, fetchOptions),
        timeout
      );
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      return await response.json();
    } catch (error) {
      if (error.message.includes('超時')) {
        throw new Error('請求超時,請檢查網絡連接');
      }
      throw error;
    }
  }
  
  get(endpoint, timeout) {
    return this.request(endpoint, { timeout });
  }
  
  post(endpoint, data, timeout) {
    return this.request(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
      timeout
    });
  }
}

// 使用示例
const apiClient = new ApiClient('https://api.example.com', 5000);

apiClient.get('/users', 3000)
  .then(users => {
    console.log('用戶列表:', users);
  })
  .catch(error => {
    console.error('獲取用戶失敗:', error.message);
  });

2. 用戶操作超時處理

// 用戶操作超時處理
class UserActionManager {
  constructor() {
    this.pendingActions = new Map();
  }
  
  // 執行用戶操作,設定超時
  async executeAction(actionId, actionPromise, timeout = 30000) {
    // 如果已有相同操作在執行,先取消
    if (this.pendingActions.has(actionId)) {
      this.cancelAction(actionId);
    }
    
    const { promise, cancel } = cancellablePromise(actionPromise);
    
    // 儲存取消函數
    this.pendingActions.set(actionId, cancel);
    
    try {
      const result = await timeoutCancellablePromise(promise, timeout);
      this.pendingActions.delete(actionId);
      return result;
    } catch (error) {
      this.pendingActions.delete(actionId);
      
      if (error.message.includes('超時')) {
        // 顯示用戶友好的超時提示
        this.showTimeoutMessage(actionId);
      }
      
      throw error;
    }
  }
  
  // 取消特定操作
  cancelAction(actionId) {
    const cancel = this.pendingActions.get(actionId);
    if (cancel) {
      cancel();
      this.pendingActions.delete(actionId);
    }
  }
  
  // 取消所有操作
  cancelAllActions() {
    this.pendingActions.forEach(cancel => cancel());
    this.pendingActions.clear();
  }
  
  // 顯示超時提示
  showTimeoutMessage(actionId) {
    console.warn(`操作 ${actionId} 超時,請稍後重試`);
    // 這裡可以顯示 UI 提示
  }
}

// 使用示例
const actionManager = new UserActionManager();

// 模擬長時間操作
const longOperation = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('操作完成');
    }, 5000);
  });
};

// 執行操作,設定 3 秒超時
actionManager.executeAction('upload-file', longOperation(), 3000)
  .then(result => {
    console.log('操作成功:', result);
  })
  .catch(error => {
    console.error('操作失敗:', error.message);
  });