手写Promise

Promise介绍

1. 概念

JavaScript中的Promise是一种用于处理异步操作的对象,它代表了一个尚未完成但最终会完成或失败的操作,目的是更加优雅地书写复杂的异步任务。

2. 状态

  • Promise有三种状态:pending(待定)、fulfilled(已成功)和rejected(已失败)。
  • 当一个Promise被创建时,它处于pending状态。
  • 当操作成功完成时,Promise会转换为fulfilled状态,并返回一个值。
  • 当操作失败时,Promise会转换为rejected状态,并返回一个原因(通常是一个Error对象)。

3. 构造函数

  • Promise是通过构造函数创建的,其参数是一个执行器函数,该函数接受两个参数:resolve和reject。
  • resolve函数用于将Promise状态从pending转换为fulfilled,并传递一个值作为成功的结果。
  • reject函数用于将Promise状态从pending转换为rejected,并传递一个原因作为失败的结果。

4. then()

  • Promise对象具有then()方法,用于指定在Promise状态变为fulfilled或rejected时执行的回调函数。

  • then()方法接受两个参数:一个用于处理成功状态的回调函数,一个用于处理失败状态的回调函数(可选)。

  • then()方法返回一个新的Promise,允许链式调用。

5. catch()

  • Promise对象具有catch()方法,用于捕获Promise链中任何一步的错误。
  • catch()方法等效于then(null, onRejected),用于处理Promise链中的rejected状态。

6. 链式调用

  • 由于then()方法和catch()方法都返回Promise对象,因此可以通过链式调用来处理多个异步操作。
  • 这种方式使得异步代码的结构更清晰、更易读。

Promise的使用

模拟一个异步操作(在1秒后返回一个随机数)。如果随机数小于0.5,则将Promise状态设置为fulfilled,并通过resolve函数传递结果值;否则,将Promise状态设置为rejected,并通过reject函数传递错误原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const randomNum = Math.random();
if(randomNum < 0.5){
resolve(randomNum);
} else {
reject(new Error('Operation failed'));
}
}, 1000)
});
promise
.then((result) => {
console.log('successed:', result);
})
.catch(error => {
console.log('failed:', error);
})

Promise的应用场景

  • Ajax 请求: 使用 Promise 可以更方便地管理 Ajax 请求。例如,在使用 Fetch API 或其他 Ajax 库时,可以将异步操作封装在 Promise 中,以便更好地处理成功和失败的情况。

  • 定时器: 可以使用 Promise 来管理定时器操作,例如延迟执行某个任务或定时轮询服务器。

  • 文件操作: 在处理文件读取、写入或上传等操作时,Promise 可以提供更清晰的代码结构和错误处理机制。

  • 处理回调地狱: Promise 可以解决回调地狱(Callback Hell)的问题,通过链式调用的方式使代码更加清晰和易于理解。

  • 并行处理: 使用 Promise.all() 方法可以并行处理多个异步操作,并在它们全部完成后执行某个操作。

  • 模块加载: 在使用模块加载器(如 RequireJS 或 SystemJS)时,可以使用 Promise 来管理模块的异步加载过程。

  • 动画和 UI: 在处理动画效果或用户界面交互时,Promise 可以用于管理异步操作,例如动画的开始和结束。

  • 路由跳转: 在前端单页应用程序(SPA)中,路由跳转通常涉及异步加载组件或数据,Promise 可以用于管理路由跳转的异步操作。

总的来说,Promise 可以在任何需要处理异步操作的场景中发挥作用,并且可以提供更清晰、更可靠的代码结构和错误处理机制。

手写一个Promise

1. 创建 Promise 构造函数

首先,创建一个 Promise 构造函数。这个构造函数接受一个 executor 函数,这个函数有两个参数:resolve 和 reject,它们用于将 Promise 从 pending 状态转换为 fulfilled 或 rejected 状态。

2. 定义 Promise 的状态和值

在构造函数中,需要定义 Promise 的状态(初始为 pending)和值。状态表示 Promise 的当前状态(pending、fulfilled、rejected),值表示 Promise 的最终结果值(成功时是结果值,失败时是错误原因)。

3. 实现 resolve 和 reject 函数

在构造函数中,需要实现 resolve 和 reject 函数,它们用于将 Promise 从 pending 转换为 fulfilled 或 rejected 状态。当调用 resolve(value) 时,Promise 状态变为 fulfilled,并将 value 存储为结果值;当调用 reject(reason) 时,Promise 状态变为 rejected,并将 reason 存储为错误原因。

4. 处理异步操作

在 executor 函数中,通常会包含异步操作,例如网络请求、定时器等。你需要确保在异步操作完成后,调用 resolve 或 reject 函数来改变 Promise 的状态。

5. 实现 then 方法

then 方法是 Promise 实例的核心方法,它用于注册成功和失败的回调函数,以便在状态变为 fulfilled 或 rejected 时执行相应的操作。在 then 方法中,你需要考虑以下几点:

  • 如果 Promise 的状态已经是 fulfilled,则立即执行成功回调。
  • 如果 Promise 的状态已经是 rejected,则立即执行失败回调。
  • 如果 Promise 的状态是 pending,则将成功和失败回调分别添加到回调队列中,等待状态改变后执行。

6. 支持链式调用

需要返回一个新的 Promise 对象,以支持链式调用。这是通过在 then 方法内创建并返回一个新的 Promise 对象来实现的。

7. 处理回调返回值

在 then 方法中,你需要处理成功回调和失败回调的返回值。如果返回值是一个 Promise,需要等待这个 Promise 的状态变化,并根据其状态来决定新 Promise 的状态。如果返回值不是 Promise,直接将其作为新 Promise 的值。

8. 实现 catch 方法

catch 方法是 then 方法的一种简化形式,用于注册失败回调。你可以在 catch 方法内部调用 then(null, onRejected) 来实现。

9. 静态方法实现

为 Promise 添加静态方法,如 Promise.resolve 和 Promise.reject,以便创建已解决或已拒绝的 Promise。另外,你还可以实现 Promise.all 和 Promise.race 等静态方法,用于处理多个 Promise 实例。

10. 错误处理

在所有涉及到异步操作的地方,要确保捕获异常并将其传递给 reject 函数,以便在 Promise 失败时能够正确处理错误。

11. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
class MyPromise{
constructor(executor){
// 初始化状态为pending
this.state = 'pending';
// 初始化值为undefined
this.value = undefined;
// 初始化错误原因
this.reason = undefined;
// 初始化成功和失败的回调队列
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 定义resolve方法
const resolve = (value) => {
if(this.state === 'pending'){
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback(this.value));
}
}
// 定义reject方法
const reject = (reason) => {
if(this.state === 'pending'){
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback(this.reason));
}
}

try{
// 执行并传入resolve、reject方法
executor(resolve, reject);
} catch(error){
reject(error);
}
}

// then
then(onFulfilled, onRejected){
return new MyPromise((resolve, reject) => {
// 封装成功时执行的函数
const onFulfilledHandler = (value) => {
try{
if(typeof onFulfilled === 'function'){
const result = onFulfilled(value);
this.resolvePromise(result, resolve, reject);
} else {
resolve(value);
}
} catch(error){
reject(error);
}
};
// 封装失败时执行的函数
const onRejectedHandler = (reason) => {
try{
if(typeof onRejected === 'function'){
const result = onRejected(reason);
this.resolvePromise(result, resolve, reject);
} else {
reject(reason);
}
} catch (error){
reject(error);
}
}
// 根据当前状态执行不同处理函数
if(this.state === 'fulfilled'){
onFulfilled(this.value);
} else if(this.state === 'rejected'){
onRejected(this.reason);
} else if(this.state === 'pending') {
this.onFulfilledCallbacks.push(onFulfilledHandler);
this.onRejectedCallbacks.push(onRejectedHandler);
}
})
}

// catch
catch(onRejected) {
return this.then(null, onRejected);
}

resolvePromise(result, resolve, reject) {
if (result === this) {
return reject(new TypeError('Chaining cycle detected'));
}

let called = false;
if (result instanceof MyPromise) {
// 如果返回值是一个 Promise,则等待其状态变化
result.then(
value => {
if (called) return;
called = true;
this.resolvePromise(value, resolve, reject);
},
reason => {
if (called) return;
called = true;
reject(reason);
}
);
} else {
// 如果返回值不是 Promise,则直接将其作为新 Promise 的结果值
resolve(result);
}
}

// 静态方法resolve实现
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}

// 静态方法reject实现
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}

// 静态方法all实现
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let completed = 0;

if (promises.length === 0) {
resolve(results);
} else {
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(result => {
results[index] = result;
completed++;

if (completed === promises.length) {
resolve(results);
}
}).catch(reject);
});
}
});
}

//静态方法race实现
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve).catch(reject);
});
});
}
}

总结

Promise 是一种用于处理异步操作的 JavaScript 对象,它提供了一种更清晰、更可靠的方式来管理异步代码。以下是 Promise 的主要特点和优势的总结:

  • 简化异步操作:Promise 可以帮助我们更轻松地处理异步操作,以及在操作完成后执行相应的逻辑。

  • 链式调用:通过使用 .then() 方法,我们可以将多个异步操作链接在一起,形成一个链式调用,使代码更加清晰易读。

  • 明确的状态变化:Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),可以明确地表示异步操作的状态。

  • 错误处理机制:Promise 提供了 .catch() 方法用于捕获异常和错误,并提供了更好的错误处理机制。

  • 并行处理:使用 Promise.all() 方法,我们可以并行处理多个异步操作,并在它们全部完成后执行某个操作。

  • 避免回调地狱:Promise 可以解决回调地狱问题,使代码结构更加清晰和易于理解。

  • 广泛应用:Promise 在处理 Ajax 请求、定时器操作、文件操作、动画和 UI 交互等各种场景中都有广泛的应用。

总而言之,Promise 是一种强大的工具,可以帮助我们更好地处理异步操作,并提供了更清晰、更可靠的代码结构和错误处理机制。它是现代 JavaScript 开发中不可或缺的一部分。