本文详细介绍 ES6 中 Promise 的创建、状态管理及链式调用机制。通过对比传统回调函数,阐述如何利用 then、catch 和 finally 解决回调地狱问题,并解析 all 与 race 等静态方法的并行处理逻辑,帮助开发者掌握现代 JavaScript 异步编程的核心技巧。
Promise 概述与回调地狱问题
本文通过具体实例解析 ES6 中 Promise 的核心用法,旨在为读者提供实用的参考与学习素材。
JavaScript 采用单线程执行模型,对于耗时较长的 I/O 操作,通常依赖异步回调函数来处理。
然而,这种模式存在明显缺陷:当后续操作依赖前一个操作的结果时,代码必须嵌套在前一个操作的回调函数内部。层层嵌套会导致代码结构混乱,形成所谓的“回调地狱”。
$.get('/login.php', function (login) {
$.get('/user.php', function (user) {
$.get('/info.php', function (info) {
//代码就这样一层嵌一层,不够直观,维护也麻烦
});
});
});
为应对这一挑战,ES6 标准引入了 Promise 机制,旨在优化异步代码的编写方式。
Promise 构造函数与状态管理
Promise 本质上是一个构造函数,利用它可以实例化一个 Promise 对象。
let p = new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('OK');
resolve('OK');
}, 1000);
});
该构造函数的参数是一个执行函数,该函数接收 resolve 和 reject 两个回调函数作为参数。
其中,resolve 函数用于将 Promise 的状态标记为 fulfilled(已完成),而 reject 函数则用于将状态标记为 rejected(已失败)。
若直接执行上述代码,程序会在运行 1 秒后输出 'OK'。因此需注意,实际使用中通常需要将 Promise 的创建过程封装在函数中,并在外部调用该函数以触发执行。
let p = function () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('OK');
resolve('OK');
}, 1000);
});
};
p();
then 链式调用与返回值处理
调用上述函数 p() 将返回一个 Promise 实例,该实例提供了 then()、catch() 和 finally() 等常用方法。
then 方法接收两个参数:onFulfilled 和 onRejected,二者均为函数类型。
onFulfilled 用于处理 resolve 传递的成功数据,而 onRejected 用于处理 reject 传递的错误数据。
let p = function () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('OK');
} else {
reject('ERR');
}
}, 1000);
});
};
p().then(function (data) {
console.log('fulfilled', data);
}, function (err) {
console.log('rejected', err);
});
值得注意的是,then() 方法始终返回一个新的 Promise 实例,这使得链式调用成为可能。
在 then 方法的回调中,既可以 return 普通值,也可以 return 另一个 Promise 对象。
若直接 return 一个具体数值,then 方法会返回一个新的 Promise 实例,并以该数值作为参数调用 resolve。
let p = function () {
return new Promise(function (resolve, reject) {
resolve(1);
});
};
p().then(function (data) {
console.log(`第 ${data} 次调用`);
//注意这里直接返回的值
//then会创建一个新的Promise对象,并且以返回的值进行resolve
//那么该值会被下面的then方法的onFulfilled回调拿到
return ++data;
}).then(function (data) {
console.log(`第 ${data} 次调用`);
return ++data;
}).then(function (data) {
console.log(`第 ${data} 次调用`);
return ++data;
});
若 return 的是一个 Promise 对象,其处理逻辑如下所示。
let p = function () {
return new Promise(function (resolve, reject) {
resolve(1);
});
};
p().then(function (data) {
console.log(`第 ${data} 次调用`);
return new Promise(function (resolve, reject) {
resolve(++data);
});
}).then(function (data) {
console.log(`第 ${data} 次调用`);
return new Promise(function (resolve, reject) {
resolve(++data);
});
}).then(function (data) {
console.log(`第 ${data} 次调用`);
return new Promise(function (resolve, reject) {
resolve(++data);
});
});
其最终效果与直接 return 普通数值并无二致。
既然 then() 支持链式操作,我们便可以利用这一特性重构早期的回调地狱写法。
function login() {
return new Promise(function (resolve, reject) {
$.get('/login.php', function (result) {
resolve(result);
});
});
}
function user(data) {
return new Promise(function (resolve, reject) {
$.get('/user.php', function (result) {
resolve(result);
});
});
}
function info(data) {
return new Promise(function (resolve, reject) {
$.get('/info.php', function (result) {
resolve(result);
});
});
}
login().then(function (data) {
console.log('处理login');
//把login异步处理获取的数据,传入到下一个处理中。
return user(data);
}).then(function (data) {
console.log('处理user');
//把user异步处理获取的数据,传入到下一个处理中。
return info(data);
}).then(function (data) {
console.log('处理info');
});
经过上述改造,原本层层嵌套的回调结构变得清晰易读。需注意的是,以下代码为示意性伪代码。
错误处理与 catch 机制
Promise 对象还内置了 catch 方法,用于捕获执行过程中的错误。该方法在功能上等同于 then(null, onRejected),可视为一种语法糖。
let p = function () {
return new Promise(function (resolve, reject) {
resolve('开始');
});
};
p().then(function (data) {
console.log('1');
return new Promise(function (resolve, reject) {
reject('错误1');
});
}).then(function (data) {
console.log('2');
return new Promise(function (resolve, reject) {
reject('错误2');
});
}).then(function (data) {
console.log('3');
return new Promise(function (resolve, reject) {
reject('错误3');
});
}).catch(function (reason) {
console.log(reason);
});
需要特别留意的是,一旦异步操作中发生错误,程序流程将跳转至 catch 块,后续未执行的链式操作将不再运行。
Promise 内部已封装 try-catch 机制,当代码运行出错时,会自动生成错误对象并通过 reject 抛出,最终由 catch 方法捕获处理。
let p = function () {
return new Promise(function (resolve, reject) {
resolve('开始');
});
};
p().then(function (data) {
//注意这里打印了一个未定义的变量
console.log(a);
}).catch(function (reason) {
//这里会捕获到错误
console.log('rejected');
console.log(reason);
});
静态方法:all、race 及其他
此外,Promise 构造函数还提供了 all()、race()、reject() 和 resolve() 等静态方法,这些方法无需实例化对象即可直接调用。
all() 方法用于并行执行多个异步操作,只有当所有操作均完成后,才会触发最终的回调函数。
function fn1() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('fn1');
}, 1000);
});
}
function fn2() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('fn2');
}, 2000);
});
}
function fn3() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('fn3');
}, 3000);
});
}
//all会等待所有操作完成,会把所有操作的结果放到一个数组中,传给then。
Promise.all([fn1(), fn2(), fn3()]).then(function (data) {
console.log(data);
});
race() 方法遵循“竞速”原则,即哪个异步操作最先完成,就以该结果为准,并将结果传递给 then 方法。
function fn1() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('fn1');
}, 1000);
});
}
function fn2() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('fn2');
}, 2000);
});
}
function fn3() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('fn3');
}, 3000);
});
}
//race是以谁先处理完,就以谁为准,fn1最先处理完,那fn1的结果会传给then
//注意这里fn2和fn3还是会执行,不过不会进入then了
Promise.race([fn1(), fn2(), fn3()]).then(function (data) {
console.log(data);
});
reject() 方法返回一个带有指定拒绝原因(reason)参数的 Promise 对象。
// Promise.reject('错误')
// 等同于
// new Promise(function(resolve, reject) {
// reject('错误');
// });
let p = Promise.reject('错误');
p.then(function (data) {
}).catch(function (reason) {
console.log(reason);
});
resolve() 方法根据传入的值,返回一个对应状态的 Promise 对象。
//如果传入的参数是普通值,则返回一个新Promise对象,并以该值resolve
let p1 = Promise.resolve('OK');
p1.then(function (data) {
console.log(data);
});
//如果传入的参数是一个Promise对象,则原封不动的返回该Promise对象
let obj = new Promise(function (resolve, reject) {
resolve('我是Promise对象');
});
let p2 = Promise.resolve(obj);
p2.then(function (data) {
console.log(data);
console.log(p2 === obj);
});
//如果传入的参数是一个thenable对象(带有then方法),
//会转换成Promise对象,并执行thenable对象的then方法
let then = {
then(resolve, reject) {
resolve('我是thenable对象');
}
}
let p3 = Promise.resolve(then);
p3.then(function (data) {
console.log(data);
});
//如果什么参数都不传入,则返回状态为resolved的Promise对象
let p4 = Promise.resolve();
p4.then(function (data) {
console.log(data);
}).catch(function (reason) {
console.log(reason);
});
感兴趣的用户可使用在线 HTML/CSS/JavaScript 代码运行工具:http://tools.jb51.net/code/HtmlJsRun 对上述代码的实际运行效果进行测试。
如需深入了解 JavaScript 其他相关知识,可查阅本站系列专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》。
希望本文关于 JavaScript 程序设计的阐述能对大家有所帮助。