您现在的位置是:网站首页 > Promise模式的处理异步操作文章详情

Promise模式的处理异步操作

Promise模式是JavaScript中处理异步操作的一种强大工具,它通过链式调用和状态管理简化了回调地狱问题,让异步代码更易读、更易维护。从基本用法到高级技巧,Promise模式为开发者提供了灵活且可靠的方式来管理异步流程。

Promise的基本概念

Promise对象代表一个异步操作的最终完成(或失败)及其结果值。一个Promise有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('操作成功');
    } else {
      reject('操作失败');
    }
  }, 1000);
});

promise
  .then(result => console.log(result))
  .catch(error => console.error(error));

Promise链式调用

Promise的真正威力在于其链式调用能力,可以优雅地处理多个异步操作的顺序执行:

function fetchUser(userId) {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id: userId, name: '张三' }), 500);
  });
}

function fetchPosts(userId) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(['文章1', '文章2']), 800);
  });
}

fetchUser(123)
  .then(user => {
    console.log('获取用户:', user);
    return fetchPosts(user.id);
  })
  .then(posts => {
    console.log('获取文章:', posts);
  })
  .catch(error => {
    console.error('发生错误:', error);
  });

Promise的静态方法

Promise提供了几个实用的静态方法来处理不同的异步场景:

Promise.all

等待所有Promise完成,或第一个Promise被拒绝:

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // [3, 42, "foo"]
  });

Promise.race

返回第一个完成(无论成功或失败)的Promise结果:

const promise1 = new Promise((resolve) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2])
  .then(value => {
    console.log(value); // "two"
  });

Promise.allSettled

等待所有Promise完成(无论成功或失败):

const promise1 = Promise.resolve(3);
const promise2 = new Promise((_, reject) => 
  setTimeout(reject, 100, 'error')
);

Promise.allSettled([promise1, promise2])
  .then(results => {
    results.forEach(result => console.log(result.status));
    // "fulfilled"
    // "rejected"
  });

Promise的错误处理

Promise提供了多种错误处理方式,确保异步操作中的异常能被妥善处理:

// 方式1:使用catch方法
somePromise
  .then(handleSuccess)
  .catch(handleError);

// 方式2:then的第二个参数
somePromise
  .then(handleSuccess, handleError);

// 方式3:全局捕获
window.addEventListener('unhandledrejection', event => {
  console.warn('未处理的Promise拒绝:', event.reason);
});

Promise的高级模式

Promise的延迟执行

Promise构造函数中的执行器函数会立即执行,但可以通过包装实现延迟执行:

function createDeferred() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

const { promise, resolve } = createDeferred();
setTimeout(() => resolve('延迟解决'), 1000);
promise.then(value => console.log(value)); // 1秒后输出"延迟解决"

Promise的取消模式

虽然原生Promise不支持取消,但可以通过包装实现取消功能:

function cancellablePromise(executor) {
  let cancel;
  const promise = new Promise((resolve, reject) => {
    executor(resolve, reject);
    cancel = () => reject(new Error('Promise被取消'));
  });
  return { promise, cancel };
}

const { promise, cancel } = cancellablePromise(resolve => {
  setTimeout(() => resolve('完成'), 2000);
});

setTimeout(cancel, 1000); // 1秒后取消Promise
promise.catch(error => console.log(error.message)); // "Promise被取消"

Promise与async/await的结合

async/await语法糖让Promise的使用更加直观:

async function getUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error('获取数据失败:', error);
    throw error;
  }
}

getUserData(123)
  .then(data => console.log(data))
  .catch(error => console.error(error));

Promise的性能考虑

虽然Promise提供了便利,但在性能敏感场景需要注意:

  1. 微任务队列:Promise回调作为微任务执行,可能阻塞UI渲染
  2. 内存开销:每个Promise对象都会占用内存
  3. 错误堆栈:Promise链中的错误堆栈可能不完整
// 性能测试示例
console.time('Promise创建');
for (let i = 0; i < 10000; i++) {
  new Promise(resolve => resolve());
}
console.timeEnd('Promise创建'); // 测量大量Promise创建的开销

Promise在浏览器API中的应用

现代浏览器API广泛使用Promise:

// Fetch API
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('请求失败:', error));

// 剪贴板API
navigator.clipboard.writeText('要复制的文本')
  .then(() => console.log('复制成功'))
  .catch(err => console.error('复制失败:', err));

// 地理位置API
navigator.geolocation.getCurrentPosition = () => {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject);
  });
};

Promise的常见陷阱

使用Promise时需要注意一些常见问题:

  1. Promise构造函数中的同步错误会被自动捕获:
new Promise(() => {
  throw new Error('同步错误');
}).catch(error => console.log(error)); // 会被捕获
  1. 忘记返回Promise导致链式调用中断:
// 错误示例
somePromise
  .then(() => {
    anotherPromise(); // 忘记return
  })
  .then(result => {
    // 这里result是undefined
  });
  1. 过度嵌套Promise导致回调地狱再现:
// 反模式
getUser().then(user => {
  getProfile(user.id).then(profile => {
    getPosts(profile.id).then(posts => {
      // 深层嵌套
    });
  });
});

Promise的实用技巧

Promise的缓存模式

function createCachedPromise(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const promise = fn(...args).finally(() => cache.delete(key));
    cache.set(key, promise);
    return promise;
  };
}

const cachedFetch = createCachedPromise(fetchUser);
cachedFetch(1); // 首次调用会执行
cachedFetch(1); // 返回缓存的结果

Promise的超时控制

function timeoutPromise(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('超时')), timeout)
    )
  ]);
}

timeoutPromise(fetchUser(1), 500)
  .then(user => console.log(user))
  .catch(error => console.error(error)); // 如果超过500ms会报超时错误

Promise的测试策略

测试Promise代码需要特殊考虑:

// 使用Jest测试Promise
test('fetchUser返回正确的用户数据', () => {
  return fetchUser(1).then(user => {
    expect(user).toEqual({ id: 1, name: '张三' });
  });
});

// 测试异步错误
test('fetchUser处理错误情况', () => {
  expect.assertions(1);
  return fetchUser(-1).catch(error => {
    expect(error.message).toBe('无效的用户ID');
  });
});

// 使用async/await语法测试
test('fetchPosts返回用户文章', async () => {
  const posts = await fetchPosts(1);
  expect(posts).toHaveLength(2);
});

Promise在Node.js中的特殊应用

Node.js的util模块提供了promisify工具,可将回调式函数转换为Promise形式:

const fs = require('fs');
const { promisify } = require('util');

const readFile = promisify(fs.readFile);

readFile('example.txt', 'utf8')
  .then(content => console.log(content))
  .catch(error => console.error('读取文件失败:', error));

对于更复杂的回调模式,可以自定义promisify函数:

function customPromisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (error, ...results) => {
        if (error) {
          return reject(error);
        }
        resolve(results.length === 1 ? results[0] : results);
      });
    });
  };
}

Promise的浏览器兼容性处理

在不支持Promise的环境下,可以使用polyfill:

<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>

或者通过构建工具引入core-js的polyfill:

import 'core-js/stable/promise';

对于需要同时支持Promise和回调的库,可以提供两种API:

function dualAPI(arg, callback) {
  if (typeof callback === 'function') {
    // 回调模式
    asyncOperation(arg, callback);
    return;
  }
  // Promise模式
  return new Promise((resolve, reject) => {
    asyncOperation(arg, (error, result) => {
      if (error) reject(error);
      else resolve(result);
    });
  });
}

Promise与生成器函数的结合

在async/await之前,常使用生成器函数配合Promise来管理异步流程:

function runGenerator(generatorFunc) {
  const generator = generatorFunc();
  
  function handle(result) {
    if (result.done) return Promise.resolve(result.value);
    return Promise.resolve(result.value)
      .then(res => handle(generator.next(res)))
      .catch(err => handle(generator.throw(err)));
  }
  
  return handle(generator.next());
}

runGenerator(function* () {
  try {
    const user = yield fetchUser(1);
    const posts = yield fetchPosts(user.id);
    console.log(posts);
  } catch (error) {
    console.error(error);
  }
});

Promise在Web Worker中的应用

Promise可以很好地与Web Worker配合使用:

// main.js
const worker = new Worker('worker.js');

function workerPromise(message) {
  return new Promise((resolve) => {
    worker.onmessage = (e) => resolve(e.data);
    worker.postMessage(message);
  });
}

workerPromise('heavy task')
  .then(result => console.log('Worker完成:', result));

// worker.js
self.onmessage = function(e) {
  const result = doHeavyTask(e.data);
  self.postMessage(result);
};

Promise的可观察性扩展

将Promise与可观察模式结合,可以创建更强大的异步抽象:

class ObservablePromise extends Promise {
  constructor(executor) {
    super(executor);
    this._observers = [];
  }

  then(onFulfilled, onRejected) {
    const newPromise = super.then(onFulfilled, onRejected);
    newPromise._observers = this._observers;
    return newPromise;
  }

  subscribe(observer) {
    this._observers.push(observer);
    return () => {
      this._observers = this._observers.filter(o => o !== observer);
    };
  }
}

const promise = new ObservablePromise(resolve => {
  setTimeout(() => resolve('完成'), 1000);
});

promise.subscribe({
  next: value => console.log('进度:', value),
  complete: () => console.log('完成')
});

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步