魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - 實作 Promise Sleep 函數

學習如何實作 JavaScript 中的 Promise Sleep 函數,處理延遲執行、定時任務與動畫,掌握非同步程式設計的基礎工具。

影片縮圖

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

Promise Sleep 函數概述

Promise Sleep 函數是一個簡單但實用的工具,它能讓程式碼暫停執行指定的時間。在各種非同步操作中,sleep 函數非常有用,尤其是需要延遲執行、模擬網路延遲或控制動畫節奏時。

為什麼需要 Sleep 函數?

  • 1. 控制執行節奏:在連續操作之間添加延遲,避免過度頻繁的操作
  • 2. 模擬網路延遲:測試程式碼對延遲的處理能力
  • 3. 動畫與視覺效果:控制連續視覺變化的間隔
  • 4. API 限流:避免短時間內發送過多請求
  • 5. 重試機制:實現指數退避等重試策略

基本 Promise Sleep 實作

下面是一個簡單的 Promise Sleep 函數實作:

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 使用範例
async function example() {
  console.log('開始');
  
  await sleep(2000); // 暫停 2 秒
  
  console.log('2秒後'); // 2秒後執行
}

example();

這個函數創建了一個新的 Promise,在指定的毫秒數後調用 resolve。結合 async/await 語法,可以讓程式碼暫停執行指定的時間。

進階 Promise Sleep 功能

我們可以擴展基本的 sleep 函數,增加更多功能:

帶返回值的 Sleep

function sleepWithValue(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

// 使用范例
async function example() {
  console.log('开始');
  
  const result = await sleepWithValue(2000, '延迟返回的数据');
  
  console.log(result); // 2秒后显示:延迟返回的数据
}

example();

可取消的 Sleep

function cancellableSleep(ms) {
  let timeoutId;
  
  const promise = new Promise(resolve => {
    timeoutId = setTimeout(resolve, ms);
  });
  
  // 添加取消方法
  promise.cancel = function() {
    clearTimeout(timeoutId);
  };
  
  return promise;
}

// 使用範例
async function example() {
  console.log('開始');
  
  const sleepPromise = cancellableSleep(5000);
  
  // 2秒後取消休眠
  setTimeout(() => {
    console.log('取消休眠');
    sleepPromise.cancel();
  }, 2000);
  
  try {
    await sleepPromise;
    console.log('5秒後'); // 不會執行,因為已取消
  } catch (error) {
    console.log('Sleep 被取消');
  }
}

example();

實際應用場景

以下是一些 Promise Sleep 函數的實際應用:

1. 實現 API 請求重試機制

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  const { retryDelay = 1000 } = options;
  
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      // 嘗試發起請求
      const response = await fetch(url, options);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      console.log(`嘗試 ${attempt + 1} 失敗: ${error.message}`);
      lastError = error;
      
      if (attempt < maxRetries - 1) {
        // 使用指數退避策略計算下次重試延遲
        const delay = retryDelay * Math.pow(2, attempt);
        console.log(`等待 ${delay}ms 後重試...`);
        await sleep(delay);
      }
    }
  }
  
  throw lastError;
}

// 使用範例
async function getData() {
  try {
    const data = await fetchWithRetry('https://api.example.com/data');
    console.log('成功獲取數據:', data);
  } catch (error) {
    console.error('所有重試都失敗了:', error);
  }
}

getData();

2. 創建動畫序列

async function animateSequence(element) {
  // 設置初始狀態
  element.style.opacity = '0';
  element.style.transform = 'translateY(20px)';
  element.style.transition = 'all 0.5s ease-out';
  
  // 確保元素可見
  element.style.display = 'block';
  
  // 等待一幀以確保過渡效果生效
  await sleep(50);
  
  // 淡入動畫
  element.style.opacity = '1';
  element.style.transform = 'translateY(0)';
  
  // 等待淡入完成
  await sleep(500);
  
  // 添加強調效果
  element.style.transform = 'scale(1.1)';
  await sleep(200);
  
  // 恢復正常大小
  element.style.transform = 'scale(1)';
}

// 使用範例
document.getElementById('showButton').addEventListener('click', () => {
  const notification = document.getElementById('notification');
  animateSequence(notification);
});

3. 限流 API 請求

class RateLimiter {
  constructor(requestsPerSecond) {
    this.minDelayMs = 1000 / requestsPerSecond;
    this.lastRequestTime = 0;
  }
  
  async limit() {
    const now = Date.now();
    const timeElapsed = now - this.lastRequestTime;
    
    if (timeElapsed < this.minDelayMs) {
      // 計算需要等待的時間
      const waitTime = this.minDelayMs - timeElapsed;
      await sleep(waitTime);
    }
    
    this.lastRequestTime = Date.now();
  }
}

// 使用範例 - 每秒最多發送2個請求
const limiter = new RateLimiter(2);

async function fetchWithRateLimit(url) {
  await limiter.limit();
  return fetch(url).then(res => res.json());
}

// 示範多次調用
async function fetchMultipleData() {
  console.log('開始獲取數據...');
  
  // 這些請求將被限流,每秒最多執行2個
  const results = await Promise.all([
    fetchWithRateLimit('https://api.example.com/1'),
    fetchWithRateLimit('https://api.example.com/2'),
    fetchWithRateLimit('https://api.example.com/3'),
    fetchWithRateLimit('https://api.example.com/4')
  ]);
  
  console.log('所有數據獲取完成', results);
}

fetchMultipleData();