一、Promise基本介绍
Promise从ES6开始被加入到了JavaScript中,是一种处理异步编程的新思路,用于解决传统回调函数带来的回调层数嵌套问题,是一种更加优雅的方式来处理异步流程。Promise可以看作是一个容器,里面保存着未来会被处理的异步操作的结果,可以通过then方法来获取异步操作的结果或处理异步操作失败的情况。
二、Promise的基本使用
基本的Promise使用方式如下所示:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = '成功!';
resolve(result);
}, 1000);
});
promise.then(result => {
console.log(result); // 成功!
}).catch(error => {
console.log(error);
});
在上面的示例中,Promise通过构造函数来创建,resolve函数表示Promise对象中异步操作成功完成时的处理函数,reject函数表示异步操作失败时的处理函数,then函数表示对异步操作成功时的处理,catch函数表示对异步操作失败时的处理。
三、Promise的特点
Promise的特点有以下几个方面:
- 不可逆:Promise一旦进入resolved或rejected状态后,便无法再次改变状态。
- 链式调用:then和catch方法返回的是另一个Promise对象,因此可以实现链式调用。
- 延迟绑定:Promise的then和catch方法会返回一个新的Promise对象,在后面的then和catch中可以再次绑定新的处理函数。
- 处理并行异步操作:Promise.all方法可以处理多个异步操作,当所有操作都成功后才返回结果。
- 处理多个异步操作中的任意一个:Promise.race方法会在任何一个异步操作完成时返回结果。
四、Promise内部实现原理
Promise的内部实现原理可以分为以下三个方面:状态管理、队列管理和错误处理。
1、状态管理
Promise内部需要管理三种状态:Pending、Resolved(Fulfilled)和Rejected。当Promise被创建后,初始状态为Pending。当异步操作状态变为成功时,Promise状态会转换为Resolved,Promise对象状态变为Rejected则代表异步操作失败。因为Promise状态不可逆,所以一旦状态变为Resolved或Rejected,Promise实例便不可再被修改。
Promise内部的状态管理可以使用一个变量来代表Promise状态,比如下面的示例:
class MyPromise {
constructor(task) {
this.status = 'Pending';
task(this.resolve.bind(this), this.reject.bind(this));
}
resolve() {
this.status = 'Resolved';
}
reject() {
this.status = 'Rejected';
}
}
2、队列管理
Promise在内部也需要管理两个队列:FulfilledQueue和RejectedQueue,这里可以使用一个数组来保存。如果异步操作的状态为Resolved,可以从FulfilledQueue中获取then方法注册的回调函数执行;如果异步操作的状态为Rejected,则可以从RejectedQueue中获取catch方法注册的回调函数执行。由于Promise的链式调用,每次then操作会返回一个新的Promise实例,因此需要将FulfilledQueue和RejectedQueue继承到新的Promise实例中。
下面是Promise内部队列管理的简单实现:
class MyPromise {
constructor(task) {
this.status = 'Pending';
this.fulfilledQueue = [];
this.rejectedQueue = [];
task(this.resolve.bind(this), this.reject.bind(this));
}
then(onFulfilled) {
this.fulfilledQueue.push(onFulfilled);
return new MyPromise(() => {});
}
catch(onRejected) {
this.rejectedQueue.push(onRejected);
return new MyPromise(() => {});
}
resolve() {
this.status = 'Resolved';
this.fulfilledQueue.forEach(fn => fn());
}
reject() {
this.status = 'Rejected';
this.rejectedQueue.forEach(fn => fn());
}
}
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功!');
}, 1000);
});
promise.then(result => {
console.log(result); // 成功!
});
3、错误处理
在Promise中,错误可以通过调用reject方法来处理,并将错误信息传递给catch方法进行处理。在Promise中,如果一个Promise对象没有被任何then或catch方法所处理,错误信息会被静默丢失。因此,意外的错误应该总是被从Promise链中提取出来,并处理掉,否则会对整个系统造成很大的危害。
下面是一个错误的Promise示例:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
// 这里没有调用reject函数处理错误
const result = '成功!';
resolve(result);
}, 1000);
});
promise.then(result => {
console.log(result); // 成功!
});
在上面的示例中,如果异步操作失败,所有的错误信息都会被静默丢失,并且后续的其他操作都不会收到任何错误信息。正确的做法应该是在Promise中加入错误处理函数,当异步操作失败时会调用reject函数,通过catch方法获取到错误信息并进行处理。
下面是一个正确的Promise示例:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
const result = '成功!';
resolve(result);
} else {
const error = '失败!';
reject(error);
}
}, 1000);
});
promise.then(result => {
console.log(result); // 成功!
}).catch(error => {
console.log(error); // 失败!
});
五、Promise原理的应用
Promise原理在实际的应用中也是非常广泛的。比如jQuery中的ajax请求就是基于Promise进行封装的,可以使用Promise的then和catch方法进行回调处理。另外,一些流行的框架如Vue.js和AngularJS也都内置了Promise功能,并且可以通过链式调用方便地进行异步操作。
下面是一个基于Promise的ajax请求示例:
function ajax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.statusText);
}
}
};
xhr.open('GET', url, true);
xhr.send(null);
});
}
ajax('https://jsonplaceholder.typicode.com/todos/1').then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
六、总结
本文对Promise的基本概念、基本使用、特点、内部实现原理以及应用进行了详细的阐述。Promise的链式调用和错误处理功能可以方便地解决异步编程过程中的问题,因此在实际应用中应该选择Promise以便更加便捷地实现异步编程。同时,在使用Promise时也需要注意一些问题,比如错误处理和状态管理,以便更加完善地使用Promise。