本文深入解析 ES6 中的 Promise 对象,阐述其解决回调地狱的核心价值。详细讲解 Pending、Resolved、Rejected 三种状态及其不可逆转换机制,并逐一剖析 then、catch、all、race、resolve 及 reject 等关键 API 的使用场景与链式调用原理,帮助开发者掌握现代 JavaScript 异步编程的最佳实践。
Promise 的基本概念与状态机制
Promise 对象在 ES6 规范中早已引入,其主要目的是为了解决传统回调函数嵌套过深导致的“回调地狱”问题。本文旨在深入探讨这一机制,参考内容基于阮一峰老师的教程进行整理与解读。
第一部分:Promise 的基本概念
通过本文末尾的综合示例,可以快速理解其核心逻辑。
Promise 是 ES6 规范内置的一个对象(本质上是一个构造函数)。通过该构造函数,我们可以创建 Promise 实例,它专门用于处理异步操作。Promise 的英文原意为“承诺”。
Promise 具备以下显著特征:
• 状态机制:Promise 拥有三种状态:Pending(进行中)、Resolved(已完成,也称 Fullfilled)和 Rejected(已失败)。状态仅由异步操作的结果决定,任何外部操作均无法改变当前状态。这是 Promise 的核心定义。
• 状态转换:Promise 仅有两种状态转换路径:其一,从 Pending 转换为 Resolved;其二,从 Pending 转换为 Rejected。一旦状态发生转换,便不可逆,即使后续操作也无法改变结果。
尽管 Promise 优势明显,但也存在局限性:在 Pending 状态下,无法取消操作;此外,无法区分当前 Pending 状态是刚刚初始化还是即将完成。
Promise 的实际应用与链式调用
第二部分:Promise 的实际应用
前置说明:
在后续示例中,我们将频繁使用 setTimeout 函数。首先明确 setTimeout 的关键用法。最常用的形式如下:
setTimeout(func(){}, 1000);
即在延迟 1000ms 后执行 func 函数。实际上,setTimeout 支持传入更多参数。相关博客已详细讲解,此处为理解后续示例,只需知晓在 Chrome 浏览器中,可以传入第三、第四个及更多参数,作为 func() 函数的额外参数传递。示例如下:
setTimeout(function (a, b) {
console.log(a + b);
}, 1000, 20, 50);
最终输出结果为:70
以下代码展示了一个 Promise 实例的创建过程:
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
由于 Promise 是构造函数,我们使用 new 关键字创建对象。需要注意的是,必须提供 function(resolve, reject){} 函数,否则无法构成有效的 Promise。
该函数用于初始化 Promise 对象,接收两个函数作为参数。若在函数体内执行 resolve 函数,Promise 状态将由 Pending 转化为 Resolved(或 Fullfilled,两者等价);同理,若执行 reject 函数,状态将变为 Rejected。
注意:示例中的 if 语句并非必需,此处旨在说明逻辑:当异步操作成功时,调用 resolve 将 Pending 转为 Resolved,并传入成功后的值 value 供后续使用。之所以称为“供后续使用”,是因为 Promise 提供了 then() 方法,用于定义异步成功(或失败)后的处理逻辑。这正是 resolve 和 reject 内置函数的存在意义。
创建 Promise 对象后,结果必然存在,但成功或失败尚不确定。我们需要根据结果执行不同操作,此时使用 then() 方法,如下所示:
promise.then(function(value) {
// success
}, function(error) {
// failure
});
以下是一个包含详细注释的示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>testsettimeout</title>
</head>
<body>
<script>
var promise = new Promise(function (resolve, reject) {
console.log("good");
var a = 10;
var b = a + 25;
if ( b === 35 ) {
// 一旦异步执行成功,我们就调用内置的 resolve函数,将pending状态转化为resolved,并且传入我们希望传出的执行成功后的结果。
// 注意: 这里一旦状态转变,那么后面就一定会调用then方法中的第一个参数的函数,然后将我们传入给resolve的参数传给then方法中的第一个方法作为参数,我们就可以在then的第一个方法中使用了。
resolve(b);
} else {
reject("异步执行失败");
}
});
promise.then(function (value) {
console.log("异步执行成功,输出执行结果:" + value);
}, function (error) {
console.log("异步执行失败,输出执行结果:" + error);
});
</script>
</body>
</html>
阮一峰老师提供的下述示例清晰展示了 Promise 的异步特性:
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('Resolved.');
});
console.log('Hi!');
最终输出结果为:Promise Resolved Hi
分析:该示例表明 Promise 的异步特性,因为在前面两段代码尚未执行完毕时,已经输出了 Hi。同时可以确定,Resolved 一定在 Promise 之后输出,顺序绝对无误。
以下示例演示了异步添加图片 URL 的场景:
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at' + url));
}
image.src = url;
});
}
若加载成功,调用 resolve 方法;若失败,调用 reject 方法。
以下示例引用自阮一峰老师封装的 Ajax 代码,因其精妙,直接参考:
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
此处逻辑应易于理解,值得注意的是 handler 处理函数的使用显得尤为巧妙。
第三部分:Promise.prototype.then()
在前一部分中,我们已介绍 then() 方法,此处需强调两点。
第一:then() 方法是定义在 Promise 原型上的方法。
第二:then() 方法支持链式调用,上一个 then() 方法返回的结果将传递给下一个 then 方法。
第一:在 Chrome 控制台输入 Promise.prototype 可见如下示例:

由此可见,Promise 原型中确实包含 then 方法。(注:若需查看 Array 内置对象有哪些方法,可直接在 Chrome 中输入 Array.prototype,即可查看对应方法列表。)
第二:then() 的作用是为 Promise 实例添加状态改变时的回调函数。前文已述,then 方法的第一个参数是 Resolved 状态的回调函数(必选);第二个参数(可选)是 Rejected 状态的回调函数。
由于 then() 支持链式调用,也可写作如下形式:
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
第一个回调函数完成后,其返回结果将作为参数传入第二个回调函数。
参考以下示例:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("Resolved: ", comments);
}, function funcB(err){
console.log("Rejected: ", err);
});
即第一个 then 又返回了一个 Promise,若该 Promise 状态变为 Resolved,则执行第二个 then 的第一个函数;若变为 Rejected,则执行第二个 then 的第二个函数。
错误处理与常用静态方法
第四部分:Promise.prototype.catch()
Promise.prototype.catch() 方法实际上是 then(null, rejection) 方法的别名,使用 catch() 纯粹是为了便于使用和理解。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
在前面的例子中,我们讲解 then() 方法接受两个参数:第一个参数是 Pending 变为 Resolved 后执行的函数(必选);第二个参数是 Pending 变为 Rejected 后执行的函数(可选)。
建议最后不要使用第二个参数,取而代之最好使用 catch(),如下所示:
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
值得注意的是:catch() 方法返回的仍是一个 Promise 对象,我们可以在其后继续使用 then 进行链式调用。
第五部分:Promise.all()
Promise.all() 方法用于将多个 Promise 实例包装成一个新的 Promise 实例,如下所示:
var p = Promise.all([p1, p2, p3]);
其中的 p1、p2、p3 均为 Promise 对象实例。若参数不是 Promise 实例,则会先调用下文提到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。
p 的状态由 p1、p2、p3 决定,分为以下两种情况:
• 仅当 p1、p2、p3 的状态都变为 fulfilled 时,p 的状态才会变为 fulfilled。此时,p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
• 只要 p1、p2、p3 中有一个被 rejected,p 的状态就变为 rejected。此时,第一个被 reject 的实例的返回值,会传递给 p 的回调函数。
让我们查看以下具体示例:
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON("/post/" + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
在此示例中,通过数组的 map 生成了 6 个 Promise 对象,并作为参数传递给 Promise.all()。只有这 6 个 Promise 对象最终返回的都是 resolved 时,才会调用 Promise.all() 后面的 then() 方法。
第六部分:Promise.race()
该方法同样是将多个 Promise 实例包装成一个新的 Promise 实例,如下:
var p = Promise.race([p1, p2, p3]);
在上述代码中,如果有一个 Promise 率先改变状态,p 的状态就会随之改变。
Promise 的异步特性与性能分析
第七部分:Promise.resolve()
若希望将现有对象转化为 Promise 对象,可使用 Promise.resolve() 方法。根据参数不同,可分为 4 种情况:
(1)参数是一个 Promise 实例
若参数是 Promise 实例,Promise.resolve 将不做任何修改,原封不动地返回该实例。
(2)参数是一个 thenable 对象
若参数是一个 thenable 对象(即具有 then() 方法的对象),Promise.resolve() 会立即将该对象转化为 Promise 对象,并立即执行 then 方法。
(3)参数不是具有 then 方法的对象,或根本就不是对象
若参数是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 Resolved。
(4)不带有任何参数
第八部分:Promise.reject()
Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。
第九部分:深入理解 Promise
其作用在于:若前面的代码非常耗时,就会阻塞后续代码片段的执行。因此,我们希望它不阻塞后续代码,便让其异步执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>testsettimeout</title>
</head>
<body>
<div class="wrap">
</div>
<script>
console.time("time");
for (var i = 0; i < 50000; i++) {
var li = document.createElement("li");
document.querySelector(".wrap").append(li);
}
console.timeEnd("time");
var a = 10;
var b = a + 25;
console.log( a);
console.log(b);
console.timeEnd("time");
</script>
</body>
</html>
最终输出结果如下:

可见前面的代码阻塞了后续代码,导致后续代码无法运行。
由于我们期望耗时代码尽快执行,以免严重影响用户体验,故将其置于最前;若其无用,则置于最后。
但我们也希望后续代码尽快执行,该如何处理?若能令前面的代码单独在一个线程上运行,而不影响后续代码运行即可。
setTimeout() 即可实现,如下所示:
console.time("time");
setTimeout(function() {
for (var i = 0; i < 50000; i++) {
var li = document.createElement("li");
document.querySelector(".wrap").append(li);
}
console.timeEnd("time");
}, 0);
var a = 10;
var b = a + 25;
console.log(a);
console.log(b);
console.timeEnd("time");
最终输出结果如下:

可见 setTimeout 中的函数一开始就执行了。
但为何 setTimeout 内容执行如此缓慢?我们再观察一个示例。
console.time("time");
setTimeout(function() {
console.timeEnd("time");
}, 0);
结果如下:
time: 2.234ms
即 setTimeout 自身就会消耗一定时间。
那么,Promise 究竟如何与它们比较?其价值何在?
价值全在于此!参考以下示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>testsettimeout</title>
</head>
<body>
<div class="wrap">
</div>
<script>
let promise = new Promise(function(resolve, reject) {
console.log('Promise! 我是Promise对象里的函数,我最早出现是因为我要给后面一个耗时的家伙提供数据a');
var a = 20;
resolve(a);
});
promise.then(function(value) {
console.log('哈哈哈,我得到数据a了,要开始执行喽。我是一个非常耗时的操作,依赖前面的数据,但是我想快一点执行,这有利于用户体验,所以别把我放在后头啊;我被放在了一个新的线程上,不会阻塞别人的');
for (var i = 0; i < 1000000; i++) {
var li = document.createElement("li");
document.querySelector(".wrap").appendChild(li);
}
console.log("执行完毕");
});
console.log('Hi! 我是什么都不依赖的程序,但我也想快一点执行,不能委屈我啊');
</script>
</body>
</html>

说明:在已执行的情况下录制屏幕,然后按下刷新键,可以发现,new Promise 里的函数是立即执行的,紧接着是 promise.then() 之后的函数立即执行,而没有等待 then() 函数。最后进入 then() 函数,说了一堆废话,最后大约 2 ~ 3s 之后执行结束。故 promise.then() 函数才是真正的异步执行。
promise 实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>promise</title>
</head>
<body>
<script>
function Mypromise(fn) {
this.state = 'pending';
this.value = void 0;
this.doneList = [];
this.failList = [];
// 在创建一个promise的时候,需要传递一个fn函数,接受参数resolve和reject。
// resolve和reject目前还没有定义,因为用户是直接调用的,如resolve('success'),所以这个resolve方法是需要我们自己来定义的。
var self = this;
function resolve(value) {
// 一旦resolve,状态机的状态由pending -> resolved
// 并且在resolve的时候状态必须是pending,才能转换状态。
// 异步执行所有回调函数
setTimeout(function () {
if (self.state == 'pending') {
self.state = 'resolved'
self.value = value;
// 一旦成功,我们就可以执行所有promise上挂载的 doneList 中所有的回调函数了,并将value值传递过去
for (var i = 0; i < self.doneList.length; i++) {
self.doneList[i](value);
}
}
}, 0);
}
function reject(reason) {
// 一旦reject,状态机的状态由pending -> rejected
// 在reject的时候状态必须是pending,才能转化状态。
// 异步执行所有回调函数
setTimeout(function () {
if (self.state == 'pending') {
self.state = 'rejected'
self.value = reason;
// 一旦失败,我么就可以把所有的 failList 调用了,并且传递 reason
for (var i = 0; i < self.failList.length; i++) {
self.failList[i](reason);
}
}
}, 0);
}
fn(resolve, reject);
}
Mypromise.prototype = {
constructor: Mypromise,
// then方法接受成功时的回调和失败时的回调
// 实际上,这里的then方法就像路由中的注册路由一样,是一个注册的过程,而没有真正的调用。
// 并且then是支持链式调用的,所以then应该返回一个promise。 如果返回旧的,那么因为状态不能改变,所以没有意义,所以我么一定要返回一个新的promise,这样这个状态机才可以再次正常的工作。
then: function (onResolved, onRejected) {
var self = this;
// 对于then而言,最终要返回一个新的promise,这样才支持链式调用。
var promise2;
// 这里要做一个判断,看传递进来的是否是一个函数,如果是,则不变,如果不是,就拒绝。
// 如果是promise.then().then().then(function (value) {}); 我们还希望拿到value,那么就要把value在即使没有用到onResolved的时候也传递下去。对于reason也是如此。
onResolved = typeof onResolved == 'function' ? onResolved : function (value) {return value;}
onRejected = typeof onRejected == 'function' ? onRejected : function (reason) { return reason;}
// 下面这一部分是比较核心的内容,因为then最终要返回一个promise,但是,这个promise究竟应该怎么返回呢? 如果在then中,用户就返回了promise,那么我们就用户的,如果用户用的不是promise,那么我么就要自己封装好这个promise了。
// 注意: promise也是需要异步调用的,所以可以使用promise进行封装。
switch(this.state) {
case 'resolved':
// 如果resolved,则返回一个新的promise
return promise2 = new Mypromise(function (resolve, reject) {
setTimeout(function () {
try {
// 这里相当于直接就给执行了,看看返回的是什么值,如果是promise,那么直接就使用这个promise了。
var x = onResolved(self.value);
if (x instanceof Mypromise) {
x.then(resolve, reject);
}
} catch (e) {
reject(e);
}
}, 0)
})
case 'rejected':
// 如果rejected,同样也需要返回一个新的promise
return promise2 = new Mypromise( function (resolve, reject) {
setTimeout(function () {
try {
var x = onRejected(self.value);
if (x instanceof Mypromise) {
x.then(resolve, reject);
}
} catch (e) {
reject(e);
}
}, 0);
})
// 如果是pending状态,我们就不能确定使用什么,等到状态确定之后才能决定。
case 'pending':
return promise2 = new Mypromise(function () {
// 注意:一般then都是执行的这里, 即在then的时候进行注册,把相应的成功的和失败的都会初测在这里,push的是一个函数,所以这里的onResolved还是没有执行的。
setTimeout(function () {
self.doneList.push(function (value) {
try {
var x = onResolved(self.value)
if (x instanceof Mypromise) {
x.then(resolve, reject)
} else {
onResolved(value);
}
} catch (e) {
reject(e)
}
});
console.log(self.doneList)
self.failList.push(function (value) {
try {
var x = onRejected(self.data);
if (x instanceof Mypromise) {
x.then(resolve, reject);
}
} catch (e) {
}
});
}, 0);
})
default:
console.log(this.state);
return;
}
},
catch: function (onRejected) {
return this.then(null, onRejected);
}
}
var promise = new Mypromise(function (resolve, reject) {
if (5 > 3) {
resolve('success');
}
});
promise.then(function (value) {
console.log('哈哈哈哈或');
});
</script>
</body>
</html>
总结
以上所述是小编给大家介绍的 ES6 中的 promise,希望对大家有所帮助。如果大家有任何疑问请给我留言,小编会及时回复大家。在此也非常感谢大家对本站网站的支持!