简单的说,Promie是ES6引入的,进行异步编程的一种新的解决方案(在ES5及之前,处理异步操作通常使用回调函数)。promise对象用来封装一个异步操作并可以获取其成功或失败的结果值。
支持链式调用,可以解决JavaScript中回调地狱(Callback Hell)问题;
指定回调函数的方式更加灵活,使异步代码更易于编写和管理。
是指在异步编程中,多个回调函数嵌套在一起形成的深层次、难以维护的代码结构。这种情况通常出现在使用回调函数处理异步操作的代码中,特别是在ES5及之前的JavaScript版本中。
asyncFunction1(opt, (...data1) => {
asyncFunction2(opt, (...data2) => {
asyncFunction3(opt, (...data3) => {
asyncFunction4(opt, (...data4) => {
//... more nested callbacks ...
});
});
});
});
在上述代码中,每个异步操作的结果都依赖于前一个操作的结果,导致嵌套的回调函数越来越多,代码变得难以维护和理解。
由此可以看出回调地狱的问题:
- 可读性差:由于多个层级的回调函数,代码变得晦涩难懂,可读性大大降低。
- 错误处理困难:在回调地狱中,错误处理需要在每个回调函数中进行,导致错误处理代码重复和冗余。
- 增加维护成本:当代码逻辑需要修改时,需要深入多层回调来理解和修改,增加了维护成本。
- Pending(进行中):初始状态,表示异步操作正在进行中。
- Fulfilled(已成功):异步操作成功完成,并返回了结果,此时Promise会进入已成功状态。
- Rejected(已失败):异步操作失败,出现错误,并返回错误信息,此时Promise会进入已失败状态。
一个Promise对象一旦进入Fulfilled(已成功)或Rejected(已失败)状态,就称为“settled”状态,这个状态不可改变。
const myPromise = new Promise((resolve, reject) => {
// 异步操作代码
// 如果操作成功,调用 resolve(value)
// 如果操作失败,调用 reject(error)
});
Promise对象的主要方法是`.then()`和`.catch()`,通过这两个方法可以处理异步操作的结果和错误:
function fetchUserData() {
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true; // 模拟成功情况,可以根据实际情况设置
if (success) {
const data = { name: "John", age: 30 };
resolve(data); // 成功时调用resolve,并传递结果
} else {
reject("Error: Unable to fetch user data"); // 失败时调用reject,并传递错误信息
}
}, 2000); // 模拟异步延迟2秒
});
}
// 使用Promise
fetchUserData()
.then((data) => {
console.log("User data:", data);
})
.catch((error) => {
console.error("Error:", error);
});
效果展示:
成功:
失败:
示例1,使用回调函数处理异步操作:
// 假设有一个异步函数,用于模拟从服务器获取数据
function fetchDataFromServer(callback) {
setTimeout(() => {
const data = { message: "Data received from server" };
// 模拟成功获取数据
callback(null, data); // 第一个参数为错误对象,null表示没有错误
// 模拟出现错误的情况
// callback("Error: Unable to fetch data");
}, 2000); // 模拟异步延迟2秒
}
// 使用回调函数获取数据
fetchDataFromServer((error, data) => {
if (error) {
console.error("Error:", error);
} else {
console.log("Data:", data);
}
});
效果展示:
成功:
失败:
在上述示例1中,fetchDataFromServer函数接受一个回调函数作为参数,并在异步操作完成后调用该回调函数。回调函数的第一个参数通常用于传递错误信息,第二个参数用于传递成功的结果。
示例2,使用Promise处理异步操作:
// 假设有一个异步函数,用于模拟从服务器获取数据
function fetchDataFromServer() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 模拟成功情况,可以根据实际情况设置
if (success) {
const data = { message: "成功接收到数据" };
resolve(data); // 成功时调用resolve,并传递结果
} else {
reject("接收数据失败!"); // 失败时调用reject,并传递错误信息
}
}, 2000); // 模拟异步延迟2秒
});
}
// 使用Promise获取数据
fetchDataFromServer()
.then((data) => {
console.log("Data:", data);
})
.catch((error) => {
console.error("Error:", error);
});
效果展示:
成功:
失败:
在上述示例2中,fetchDataFromServer函数返回一个Promise对象。通过.then()方法处理成功的情况,通过.catch()
方法处理失败的情况。
示例1和示例2对比分析:
- 使用回调函数时,代码会出现多层嵌套,可能导致回调地狱,特别是当处理多个异步操作时。
- 使用Promise时,通过链式调用
.then()
和.catch()
,代码结构更加清晰和易于阅读。
举简单的例子,在fs文件操作、数据库操作、AJAX操作以及定时器中,如何使用回调函数和Promise函数,进行对照学习。
使用回调函数:
//使用Node.js的fs模块进行文件读取
const fs = require('fs');
// 使用回调函数读取文件
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error:', err);
} else {
console.log('Data from file:', data);
}
});
效果展示:
成功:
失败:
使用Promise:
const fs = require('fs/promises');
// 使用Promise读取文件
fs.readFile('example.txt', 'utf8')
.then((data) => {
console.log('Data from file:', data);
})
.catch((err) => {
console.error('Error:', err);
});
效果展示:
成功:
首先,确保已经安装了mysql2
库(或其他MySQL连接库),然后通过该库来连接数据库。
npm install mysql2
使用回调函数:
//此处使用Mysql数据库进行数据查询
const mysql = require('mysql2');
// 创建数据库连接池
const pool = mysql.createPool({
host: 'your_database_host',
user: 'your_database_user',
password: 'your_database_password',
database: 'your_database_name',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 示例:执行查询操作
const query = 'SELECT * FROM account WHERE money > ?';
const values = [1000];
pool.getConnection((err, connection) => {
if (err) {
console.error('Error:', err);
return;
}
connection.query(query, values, (error, results) => {
connection.release();
if (error) {
console.error('Error:', error);
} else {
console.log('Query results:', results);
}
});
});
效果展示:
成功:
失败:
使用Promise:
const mysql = require('mysql2');
// 创建数据库连接池
const pool = mysql.createPool({
host: 'your_database_host',
user: 'your_database_user',
password: 'your_database_password',
database: 'your_database_name',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 封装查询操作为Promise函数
function executeQuery(query, values) {
return new Promise((resolve, reject) => {
pool.getConnection((err, connection) => {
if (err) {
reject(err);
return;
}
connection.query(query, values, (error, results) => {
connection.release();
if (error) {
reject(error);
} else {
resolve(results);
}
});
});
});
}
// 示例:执行查询操作
const query = 'SELECT * FROM account WHERE monrey > ?';
const values = [1000];
executeQuery(query, values)
.then((results) => {
console.log('Query results:', results);
})
.catch((error) => {
console.error('Error:', error);
});
效果展示:
成功:
失败:
使用回调函数:
function sendAJAX(url, successCallback, errorCallback) {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open('GET', url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
successCallback(xhr.response);
} else {
errorCallback(xhr.status);
}
}
};
}
// Example usage with callbacks
sendAJAX(
'https://api.apiopen.top/getJoke',
function (data) {
console.log('Data:', '请求成功');
},
function (error) {
console.error('Error:', '请求失败');
}
);
效果展示:
成功:
失败:
使用Promise:
function sendAJAX(url){
return new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET",url);
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 ){
//判断成功
if(xhr.status >= 200 && xhr.status <300){
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
})
}
// Example usage with Promise
sendAJAX('https://api.apiopen.top/getJoke')
.then((data) => {
console.log('Data:',"请求成功");
})
.catch((error) => {
console.error('Error:', "请求失败");
});
效果展示:
成功:
失败:
使用回调函数:
//此处使用定时器函数setTimeout和setInterval进行操作
// 使用回调函数实现定时器
function doSomething(callback) {
setTimeout(() => {
console.log('Doing something...');
callback();
}, 2000); // 2秒后执行回调
}
// 使用回调函数执行定时器操作
doSomething(() => {
console.log('Callback executed after 2 seconds.');
});
使用Promise:
// 使用Promise实现定时器
function doSomething() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Doing something...');
resolve();
}, 2000); // 2秒后resolve
});
}
// 使用Promise执行定时器操作
doSomething()
.then(() => {
console.log('Promise resolved after 2 seconds.');
});
效果展示:
综上学习,可以看到Promise函数的基本流程如图所示:
除了上述看到的Promise.resolved()、Promise.rejected()、Promise.then()、Promise.catch(),其他常用的API还有:Promise.all(iterable)、Promise.race(iterable)等等。
Promise.all(iterable):接收一个可迭代对象iterable(如数组或类数组对象),返回一个新的Promise对象。该Promise对象在iterable中的所有Promise对象都解决(Fulfilled)时才会解决,并将所有解决结果以数组形式返回;如果iterable中有任何一个Promise对象拒绝(Rejected),则该Promise对象立即拒绝,并返回第一个拒绝的原因。
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // Output: [1, 2, 3]
})
.catch((error) => {
console.error(error);
});
效果展示:
Promise.race(iterable):类似于Promise.all(),但是只要iterable中的任何一个Promise对象解决或拒绝,返回的Promise对象就会立即解决或拒绝,并采用第一个解决或拒绝的结果。
const promise1 = new Promise((resolve) => setTimeout(resolve, 1000, "one"));
const promise2 = new Promise((resolve) => setTimeout(resolve, 2000, "two"));
Promise.race([promise1, promise2])
.then((value) => {
console.log(value); // Output: "one" (promise1 resolves faster)
})
.catch((error) => {
console.error(error);
});
效果展示:
函数的返回值为promise对象,且promise对象的结果是由async函数的返回值决定。它其实是用来定义一个返回 Promise 对象的异步函数。
async function main(){
}
let result = main();
console.info(result);
此时看控制台可以看到,返回值是一个promise对象,且状态是"fulfilled"(已成功)。
1.如果返回的值是一个非Promise类型的数据:
效果展示:
2.返回的是一个Promise对象:
成功:
失败:
3.直接抛出一个异常:
效果展示:
await右侧的表达式一般为promise对象,返回的是promise成功的值。若是表达式时其他值,则直接将此值作为await的返回值。
注意:await必须写在async函数中,但是async函数中可以没有await。若是await中的promise失败了,就会抛出异常,需要通过try...catch捕获处理。
1、直接使用await:
效果展示:
3.右侧为其他:
效果展示:
4. promise是失败的状态:
效果展示:
使用回调函数实现实例:
//使用Node.js的fs模块进行文件读取
const fs = require('fs');
// 使用回调函数读取文件
fs.readFile('./example.txt', 'utf8', (err, data1) => {
if(err) throw err;
fs.readFile('./example2.txt', 'utf8', (err, data2) => {
if(err) throw err;
fs.readFile('./example3.txt', 'utf8', (err, data3) => {
if(err) throw err;
console.log(data1 + data2 + data3)
});
});
});
使用async函数+await实现示例:
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);
async function main(){
try {
let data1 = await mineReadFile('./example.txt');
let data2 = await mineReadFile('./example2.txt');
let data3 = await mineReadFile('./exampl23.txt');
console.log(data1 + data2 + data3)
} catch (error) {
console.log(error)
}
}
main();
效果展示:
成功:
失败:
Promise对象是用于处理异步操作的特殊对象,通过其构造函数创建,并传递一个executor函数,内部包含异步操作,当异步操作完成时,通过resolve方法将Promise对象从Pending状态转换为Fulfilled状态,将结果传递给处理函数;若异步操作失败,则通过reject方法将Promise对象转换为Rejected状态,传递错误信息。我们可以使用.then()和.catch()来处理成功和失败的情况,使得异步编程更加清晰、简洁,避免回调地狱,提供了更强大的异步编程能力。