一、啥是 Promise
在 JavaScript 里,Promise 就像是咱生活中的一个承诺。比如说,你跟朋友约好了周末一起去看电影,这时候其实就有两种结果,要么到了周末你们真去看电影了(承诺实现),要么因为某些事儿看不成了(承诺失败)。
在代码里,Promise 也是用来处理异步操作的。异步操作就好比你在烧水的时候还能去干别的事儿,不用一直盯着水开没开。以前处理异步操作,代码会写得特别乱,嵌套很多层,Promise 就解决了这个问题。
下面给大家看个简单的 Promise 示例(技术栈:Javascript):
// 创建一个 Promise 实例
const myPromise = new Promise((resolve, reject) => {
// 模拟一个异步操作,比如网络请求
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
// 如果随机数小于 0.5,认为操作成功,调用 resolve 函数
resolve('操作成功');
} else {
// 如果随机数大于等于 0.5,认为操作失败,调用 reject 函数
reject('操作失败');
}
}, 1000);
});
// 使用 then 方法处理 Promise 成功的情况,使用 catch 方法处理 Promise 失败的情况
myPromise.then((result) => {
console.log(result);
}).catch((error) => {
console.log(error);
});
在这个例子里,我们创建了一个 Promise 实例,用 setTimeout 模拟了一个异步操作。resolve 函数就相当于承诺实现了,reject 函数就相当于承诺失败了。然后用 then 方法处理成功的结果,用 catch 方法处理失败的结果。
二、Promise 的状态
Promise 有三种状态:
- pending(进行中):就像你跟朋友约好了看电影,但是还没到周末,这事儿还在进行中,结果还不确定。
- fulfilled(已成功):到了周末,你们顺利去看电影了,承诺实现了。
- rejected(已失败):到了周末,因为某些原因看不成电影了,承诺失败了。
一旦 Promise 的状态从 pending 变成了 fulfilled 或者 rejected,就不能再变了。就好比你跟朋友已经确定看不成电影了,就不能再反悔说又能看了。
下面这个示例(技术栈:Javascript),我们来看看状态的变化:
// 创建一个 Promise 实例
const promise = new Promise((resolve, reject) => {
console.log('Promise 状态:pending');
setTimeout(() => {
// 模拟操作成功,改变状态为 fulfilled
resolve('操作完成');
console.log('Promise 状态:fulfilled');
}, 1000);
});
// 处理 Promise 成功的情况
promise.then((result) => {
console.log(result);
});
在这个例子里,一开始 Promise 状态是 pending,过了 1 秒后,调用 resolve 函数,状态变成了 fulfilled。
三、Promise 的链式调用
Promise 的一个强大之处就是可以链式调用。就好比你完成了一件事儿之后,接着去做另一件事儿。
下面这个示例(技术栈:Javascript)展示了链式调用:
// 创建第一个 Promise 实例
const firstPromise = new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟操作成功,传递结果 '第一步完成'
resolve('第一步完成');
}, 1000);
});
// 链式调用
firstPromise.then((result) => {
console.log(result);
// 返回一个新的 Promise 实例
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟操作成功,传递结果 '第二步完成'
resolve('第二步完成');
}, 1000);
});
}).then((newResult) => {
console.log(newResult);
});
在这个例子里,第一个 Promise 完成后,返回了一个新的 Promise,然后接着处理这个新的 Promise。这样就可以把多个异步操作按顺序连起来,避免了回调地狱。
四、手写一个符合规范的 Promise
现在我们来自己动手写一个简单的 Promise。
1. 基本结构
首先,我们要创建一个 MyPromise 类,它有三种状态,还有 resolve 和 reject 方法。
// 定义 MyPromise 类
class MyPromise {
// 定义三种状态
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
// 初始状态为 pending
this.status = MyPromise.PENDING;
// 存储成功的结果
this.value = null;
// 存储失败的原因
this.reason = null;
// 存储成功的回调函数
this.onFulfilledCallbacks = [];
// 存储失败的回调函数
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === MyPromise.PENDING) {
// 如果状态是 pending,将状态改为 fulfilled
this.status = MyPromise.FULFILLED;
this.value = value;
// 执行所有成功的回调函数
this.onFulfilledCallbacks.forEach((callback) => callback());
}
};
const reject = (reason) => {
if (this.status === MyPromise.PENDING) {
// 如果状态是 pending,将状态改为 rejected
this.status = MyPromise.REJECTED;
this.reason = reason;
// 执行所有失败的回调函数
this.onRejectedCallbacks.forEach((callback) => callback());
}
};
try {
// 执行 executor 函数,传入 resolve 和 reject 函数
executor(resolve, reject);
} catch (error) {
// 如果执行过程中出错,调用 reject 函数
reject(error);
}
}
then(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,将其替换为一个返回 value 的函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
// 如果 onRejected 不是函数,将其替换为一个抛出 reason 的函数
onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason; };
const newPromise = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
// 执行 onFulfilled 函数,获取结果
const result = onFulfilled(this.value);
// 处理结果
resolve(result);
} catch (error) {
// 如果执行过程中出错,调用 reject 函数
reject(error);
}
};
const handleRejected = () => {
try {
// 执行 onRejected 函数,获取结果
const result = onRejected(this.reason);
// 处理结果
resolve(result);
} catch (error) {
// 如果执行过程中出错,调用 reject 函数
reject(error);
}
};
if (this.status === MyPromise.FULFILLED) {
// 如果状态是 fulfilled,立即执行 handleFulfilled 函数
setTimeout(handleFulfilled, 0);
} else if (this.status === MyPromise.REJECTED) {
// 如果状态是 rejected,立即执行 handleRejected 函数
setTimeout(handleRejected, 0);
} else if (this.status === MyPromise.PENDING) {
// 如果状态是 pending,将 handleFulfilled 和 handleRejected 函数存储起来
this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
}
});
return newPromise;
}
}
// 使用自定义的 MyPromise
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
});
promise.then((result) => {
console.log(result);
return '新的结果';
}).then((newResult) => {
console.log(newResult);
});
2. 代码解释
- 状态管理:在
constructor里,我们把 Promise 的初始状态设为pending,还有resolve和reject方法来改变状态。 then方法:then方法用来处理 Promise 的结果。它会返回一个新的 Promise,这样就能实现链式调用。
五、应用场景
Promise 在很多场景都特别有用:
1. 网络请求
在前端开发中,经常要从服务器获取数据,这时候就可以用 Promise 来处理网络请求。
// 封装一个简单的网络请求函数,返回一个 Promise 实例
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
const data = { message: '这是从服务器获取的数据' };
// 模拟操作成功,传递数据
resolve(data);
// 可以模拟操作失败,调用 reject 函数
// reject('请求失败');
}, 1000);
});
}
// 使用封装的网络请求函数
fetchData().then((result) => {
console.log(result.message);
}).catch((error) => {
console.log(error);
});
2. 文件读取
在 Node.js 里,读取文件也是异步操作,用 Promise 可以让代码更清晰。
const fs = require('fs');
// 封装一个读取文件的函数,返回一个 Promise 实例
function readFilePromise(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (error, data) => {
if (error) {
// 如果读取文件出错,调用 reject 函数
reject(error);
} else {
// 如果读取文件成功,传递数据
resolve(data);
}
});
});
}
// 使用封装的读取文件函数
readFilePromise('test.txt').then((data) => {
console.log(data);
}).catch((error) => {
console.log(error);
});
六、技术优缺点
优点
- 避免回调地狱:前面也提到了,Promise 的链式调用让代码更清晰,不会像以前那样嵌套很多层回调函数。
- 状态管理:Promise 的状态一旦确定就不能再变,这样能避免很多错误。
缺点
- 无法取消:Promise 一旦创建就不能取消,不像有些异步操作可以中途停止。
- 错误处理复杂:在链式调用里,如果中间某个环节出错,错误处理可能会比较复杂。
七、注意事项
then方法的返回值:then方法一定要返回一个新的 Promise,这样才能实现链式调用。- 错误处理:在链式调用里,最好在最后加上
catch方法,这样能捕获前面所有的错误。
八、文章总结
通过这篇文章,我们了解了 Promise 的基本概念、状态、链式调用,还自己动手实现了一个简单的 Promise。Promise 是 JavaScript 里处理异步操作的重要工具,能让代码更清晰、更易维护。虽然它有一些缺点,但在很多场景下还是非常有用的。在实际开发中,我们要合理使用 Promise,注意它的注意事项,这样才能发挥它的最大作用。
评论