本文深入解析 ES6 Promise 的核心概念,涵盖其三种状态(pending、fulfilled、rejected)的转换规则及不可逆特性。详细讲解 then、catch、all、race、resolve 和 reject 等常用方法的使用场景与返回值逻辑,帮助开发者有效解决 Node.js 回调地狱问题,实现清晰可控的异步操作流程。
Promise 的引入背景
Node.js 的出现极大地推动了 JavaScript 在服务端领域的发展,降低了前端开发者涉足后端开发的门槛。
然而,这并不意味着开发者自动成为了全栈工程师。全栈技能要求高度集中,远不止前端掌握 HTML 和交互逻辑、后端熟悉数据库增删改查那么简单。
接触过 Node.js 的开发者都知道,其核心特征是异步回调。虽然异步机制提升了程序执行效率,但也牺牲了代码可读性。当存在多个相互依赖的异步操作时,后一步需要前一步的结果,按照 Node.js 的传统执行逻辑,往往需要通过层层嵌套来实现有序执行。
针对这一痛点,ES6 标准引入了 Promise 机制。
Promise 的基本含义与状态
含义
Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。简而言之,它专门用于处理异步操作:成功时执行相应的处理逻辑,失败时则捕获错误或终止后续操作。
其基本表示形式如下:
new Promise(
/* executor */
function(resolve, reject) {
if (/* success */) {
// ...执行代码
resolve();
} else { /* fail */
// ...执行代码
reject();
}
}
);
其中,Promise 构造函数接收一个执行器函数(executor)作为参数,该函数包含 resolve 和 reject 两个参数。执行器内部通常包含异步逻辑:若操作成功,调用 resolve() 将实例状态设为 fulfilled(已完成);若操作失败,则调用 reject() 将状态设为 rejected(已失败)。
可以将 Promise 对象类比为工厂流水线。从职能角度看,它仅有三种状态:初始状态(刚启动)、加工成功和加工失败(出现故障)。同理,Promise 对象也有三种状态:
- pending:初始状态,也称为未定状态,即初始化 Promise 并调用执行器函数后的初始状态。
- fulfilled:完成状态,表示异步操作成功。
- rejected:失败状态,表示异步操作失败。
Promise 仅有两种状态转换路径:
- 操作成功:从 pending 转换为 fulfilled。
- 操作失败:从 pending 转换为 rejected。
这种状态转换是单向且不可逆的,一旦状态确定为 fulfilled 或 rejected,就无法再回到初始的 pending 状态。
Promise 核心方法详解
方法
Promise.prototype.then()
Promise 对象包含 then 方法,调用 then() 后会返回一个新的 Promise 对象,这意味着实例化的 Promise 支持链式调用。此外,then() 方法可接收两个函数参数:一个用于处理成功结果,另一个用于处理错误结果。
示例如下:
var promise1 = new Promise(function(resolve, reject) {
// 2秒后置为接收状态
setTimeout(function() {
resolve('success');
}, 2000);
});
promise1.then(function(data) {
console.log(data); // success
}, function(err) {
console.log(err); // 不执行
}).then(function(data) {
// 上一步的then()方法没有返回值
console.log('链式调用:' + data); // 链式调用:undefined
}).then(function(data) {
// ....
});
此处主要关注 promise1.then() 调用后返回的新 Promise 对象的状态,它是 pending、fulfilled 还是 rejected?
返回的新 Promise 对象状态主要取决于 promise1.then() 方法内部的返回值,大致分为以下几种情况:
- 如果 then() 方法中返回了一个具体的参数值,那么返回的新 Promise 将变为接收状态(fulfilled)。
- 如果 then() 方法中抛出了一个异常,那么返回的新 Promise 将变为拒绝状态(rejected)。
- 如果 then() 方法中调用了 resolve(),那么返回的新 Promise 将变为接收状态(fulfilled)。
- 如果 then() 方法中调用了 reject(),那么返回的新 Promise 将变为拒绝状态(rejected)。
- 如果 then() 方法返回了一个处于未知状态(pending)的新 Promise 实例,那么返回的新 Promise 将保持未知状态。
- 如果 then() 方法没有明确指定 resolve(data)、reject(data) 或 return data,那么返回的新 Promise 默认为接收状态(fulfilled),并可继续向下传递。
状态转换实例如下:
var promise2 = new Promise(function(resolve, reject) {
// 2秒后置为接收状态
setTimeout(function() {
resolve('success');
}, 2000);
});
promise2
.then(function(data) {
// 上一个then()调用了resolve,置为fulfilled态
console.log('第一个then');
console.log(data);
return '2';
})
.then(function(data) {
// 此时这里的状态也是fulfilled, 因为上一步返回了2
console.log('第二个then');
console.log(data); // 2
return new Promise(function(resolve, reject) {
reject('把状态置为rejected error'); // 返回一个rejected的Promise实例
});
}, function(err) {
// error
})
.then(function(data) {
/* 这里不运行 */
console.log('第三个then');
console.log(data);
// ....
}, function(err) {
// error回调
// 此时这里的状态也是fulfilled, 因为上一步使用了reject()来返回值
console.log('出错:' + err); // 出错:把状态置为rejected error
})
.then(function(data) {
// 没有明确指定返回值,默认返回fulfilled
console.log('这里是fulfilled态');
});
Promise.prototype.catch()
catch() 方法与 then() 类似,均返回一个新的 Promise 对象,主要用于捕获异步操作中出现的异常。因此,通常省略 then() 方法的第二个参数,将错误处理控制权交给后续的 catch() 函数,示例如下:
var promise3 = new Promise(function(resolve, reject) {
setTimeout(function() {
reject('reject');
}, 2000);
});
promise3.then(function(data) {
console.log('这里是fulfilled状态'); // 这里不会触发
// ...
}).catch(function(err) {
// 最后的catch()方法可以捕获在这一条Promise链上的异常
console.log('出错:' + err); // 出错:reject
});
Promise.all()
Promise.all() 接收一个参数,该参数必须是可迭代的,例如数组。
它通常用于处理并发异步操作,即这些操作结果互不干扰但需异步执行。其最终状态仅有两种:成功或失败。
其状态受参数内各个值的状态影响:只有当内部所有状态均为 fulfilled 时,Promise.all() 才会变为 fulfilled;否则,只要有一个状态为 rejected,它便会变为 rejected。
成功调用后返回一个数组,数组中的值按传入参数的顺序排列,即按照传入数组的操作结果顺序返回。示例如下:
// 置为fulfilled状态的情况
var arr = [1, 2, 3];
var promises = arr.map(function(e) {
return new Promise(function(resolve, reject) {
resolve(e * 5);
});
});
Promise.all(promises).then(function(data) {
// 有序输出
console.log(data); // [5, 10, 15]
console.log(arr); // [1, 2, 3]
});
// 置为rejected状态的情况
var arr = [1, 2, 3];
var promises2 = arr.map(function(e) {
return new Promise(function(resolve, reject) {
if (e === 3) {
reject('rejected');
}
resolve(e * 5);
});
});
Promise.all(promises2).then(function(data) {
// 这里不会执行
console.log(data);
console.log(arr);
}).catch(function(err) {
console.log(err); // rejected
});
Promise.race()
Promise.race() 与 Promise.all() 类似,都接收一个可迭代的参数。不同之处在于,Promise.race() 的状态变化不受所有参数状态的影响,只要参数中有一个值的状态发生改变,该 Promise 的状态即随之改变。正如 race 一词的字面意思,谁跑得快谁赢。示例如下:
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 300, 'p1 doned');
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 50, 'p2 doned');
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, 'p3 rejected');
});
Promise.race([p1, p2, p3]).then(function(data) {
// 显然p2更快,所以状态变成了fulfilled
// 如果p3更快,那么状态就会变成rejected
console.log(data); // p2 doned
}).catch(function(err) {
console.log(err); // 不执行
});
Promise.resolve()
Promise.resolve() 接受一个参数值,可以是普通值、具有 then() 方法的对象或 Promise 实例。正常情况下,它返回一个状态为 fulfilled 的 Promise 对象。但是,当解析过程中发生错误时,返回的 Promise 对象状态将置为 rejected。示例如下:
// 参数为普通值
var p4 = Promise.resolve(5);
p4.then(function(data) {
console.log(data); // 5
});
// 参数为含有then()方法的对象
var obj = {
then: function() {
console.log('obj 里面的then()方法');
}
};
var p5 = Promise.resolve(obj);
p5.then(function(data) {
// 这里的值时obj方法里面返回的值
console.log(data); // obj 里面的then()方法
});
// 参数为Promise实例
var p6 = Promise.resolve(7);
var p7 = Promise.resolve(p6);
p7.then(function(data) {
// 这里的值时Promise实例返回的值
console.log(data); // 7
});
// 参数为Promise实例,但参数是rejected态
var p8 = Promise.reject(8);
var p9 = Promise.resolve(p8);
p9.then(function(data) {
// 这里的值时Promise实例返回的值
console.log('fulfilled:'+ data); // 不执行
}).catch(function(err) {
console.log('rejected:' + err); // rejected: 8
});
Promise.reject()
Promise.reject() 与 Promise.resolve() 相反,它接收一个参数 reason(异常原因)。此时返回的 Promise 对象状态将直接置为 rejected。示例如下:
var p10 = Promise.reject('手动拒绝');
p10.then(function(data) {
console.log(data); // 这里不会执行,因为是rejected态
}).catch(function(err) {
console.log(err); // 手动拒绝
}).then(function(data) {
// 不受上一级影响
console.log('状态:fulfilled'); // 状态:fulfilled
});
总之,除非 Promise.then() 方法内部抛出异常或明确置为 rejected 状态,否则它返回的 Promise 状态均为 fulfilled(完成态),且其状态不受上一级状态的影响。
总结与替代方案
总结
常用的方法大致介绍如上,其余方法可根据实际需求进一步了解。
解决 Node.js 回调地狱的方案不仅限于 Promise,还包括 Generator 函数以及 ES7 提出的 Async/Await 实现。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。