基本概念

定义

JavaScript ES6中引入Promise 是一种用于处理异步操作的对象(一个构造函数)。Promise 代表了一个异步操作的最终完成(或失败)及其结果值。它使得异步操作的处理更加直观,尤其是比传统的回调函数更加易于管理。

Promise 有三种状态

  • Pending(进行中):初始状态,表示异步操作正在进行中。
  • Fulfilled(已完成):表示异步操作成功完成。
  • Rejected(已拒绝):表示异步操作失败。

Promise 可以帮助解决回调地狱问题,使代码更易读、更可维护。

创建 Promise

Promise 的构造函数接受一个执行函数,该执行函数有两个参数resolverejectresolve 用于将 Promise 状态从 Pending 转换为 Fulfilledreject 用于将状态转换为 Rejected

1
2
3
4
5
6
7
8
let promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(result); // 将 Promise 状态改为 Fulfilled
} else {
reject(error); // 将 Promise 状态改为 Rejected
}
});

reject() 是 Promise的方法:Promise的静态方法 || 构造函数内部的一个参数

.then().catch() 是 Promise 实例的方法Promise.prototype.thenPromise.prototype.catch

状态转换

创建 Promise 时,此 Promise 状态为 pending,他的 result 值为 undefined

当调用 resolve() 或 reject() 时,Promise 的状态发生改变

resolve(‘成功’) 此时 Promise 状态为 fulfilled,result 值为 resolve 返回值就是’成功’

reject(‘失败’) 此时 Promise 状态为 rejected,result 值为 reject 返回值就是’失败’

状态转换的规则:

  • Pending 状态转变为 Fulfilled 状态时,Promise调用 .then() 中的成功回调函数
  • Pending 状态转变为 Rejected 状态时,Promise调用 .catch() 中的失败回调函数

当 Promise 的状态发生改变之后,promise状态不会再发生改变

基本方法

.then() 方法

当 promise 状态发生改变的时候,就会执行 .then() 方法。相反状态没有发生改变的时候 .then()不会执行

基本语法.then((value) => {console.log(value)}, (error) => {console.log(error)})

  • 用于处理 Promise 变为 Fulfilled 的情况,并且它会**返回一个新的 Promise**。

  • 传两个参数,分别是两个回调函数,value值是成功、失败的结果值。

    • 第一个是 promise 状态变为 fulfilled 时执行的回调函数
    • 第二个时状态变为 rejected 时执行的回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const myPromise = new Promise((resolve, reject) => {
    console.log('promise实例');
    resolve('成功')
    // reject('失败')
    })
    myPromise.then(
    (value) => {
    console.log('value', value);
    },
    (error) => {
    console.log(error);
    })
  • 这些方法支持链式调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const myPromise = new Promise((resolve, reject) => {
    console.log('promise实例');
    resolve('第一个成功数据')
    })
    myPromise.then((data) => {
    console.log('第一次成功:', data);
    return '这是第二个成功数据';
    })
    .then((data) => {
    console.log('第二次成功:', data);
    })
    .catch((error) => {
    console.error('失败:', error);
    });
  • 注意:

    • .then()里面的回调 return 一个值的时候,这个值不是 promise 对象,就会自动将这个值包裹成 promise 对象。【支持链式调用

      • 例:return '成功' => return Promise.resolve('成功')
    • .then().catch() 的 return 亦是如此

.catch() 方法

基本语法.catch((error) => {console.log('error',error)})

  • 用于处理 Promise 变为 Rejected 的情况,并且它会返回一个新的 Promise

    • 通常在 .then() 的第二个参数中传递失败回调函数也可以,但 .catch() 更常用于处理错误。
  • 作用是,可以捕获上层未能捕获到的错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const myPromise = new Promise((resolve, reject) => {
    console.log('promise实例');
    reject('失败')
    })
    myPromise.then((data) => {
    console.log('第一次成功:', data);
    return '这是第二个成功数据';
    })
    .then((data) => {
    console.log('第二次成功:', data);
    })
    .catch((error) => {
    console.error('失败:', error);
    });
    /** output:
    * promise实例
    * 失败: 失败
    */
    • 前两次 .then() 时,没有传递第二个参数,所以未捕获错误,在 .catch() 的时候,捕获到错误。
  • .then()抛出错误.catch()捕获

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const myPromise = new Promise((resolve, reject) => {
    console.log('promise实例');
    resolve('成功')
    })
    myPromise.then((data) => {
    console.log('第一次成功:', data);
    throw new Error('error in then')
    // return Promise.reject('error in then') // 作用同上

    // return new Error('sucess error in then'); // 返回一个 Error 对象会将其作为 Promise 的成功结果处理
    })
    .then((data) => {
    console.log('第二次成功:', data);
    })
    .catch((error) => {
    console.error('失败:', error);
    });
    /** output:
    * promise实例
    * 第一次成功: 成功
    * 失败: Error: error in then
    at /box/script.js:7:11
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    */
    • 注意三种方式的区别:
      • return new Error('error'):返回一个 Error 对象会将其作为 Promise 的成功结果处理,而不是错误。后续的 catch 不会捕获这个 Error,因为 Promise 被解析为一个正常的 Error 对象。
      • throw new Error('error'):显式抛出异常会将 Promise 状态改为拒绝(rejected),catch 会捕获到这个异常并处理它。后续的 catch 函数会被调用。
      • return Promise.reject('error'):返回一个被拒绝Promise,也会使当前 Promise 进入拒绝状态。后续的 catch 函数同样会捕获到这个错误。

.finally() 方法

.finally() 方法,无需传递参数,只要 promise 状态改变的时候,就会执行,无论状态变为 fulfilled 还是 rejected

基本语法.finally(() => {console.log('finally operation')})

  • .finally() 用于在 Promise 完成时执行一些清理操作,不论是成功还是失败。

    1
    2
    3
    4
    promise
    .then(result => { /* 处理成功结果 */ })
    .catch(error => { /* 处理错误 */ })
    .finally(() => { /* 执行清理操作 */ });

.then() 的第二个参数 和 .catch() 区别

在Promise当中抛出的错误,.then() 的第二个参数和 .catch() 捕获的时候会遵循就近原则

主要区别如果在 .then() 的第一个函数里抛出了异常,后面的 .catch() 能捕获到,而 .then() 的第二个函数捕获不到

  • .catch()只是一个语法糖,本质还是通过.then()来处理的

    1
    2
    3
    Promise.prototype.catch = function(fn){
    return this.then(null,fn);
    }

举例

throw 会在 Promise 的构造函数内部直接抛出错误Promise 会立即进入拒绝状态(rejected)。这个错误对象会被捕获。

1
2
3
const promise = new Promise((resolve, reject) => {
throw new Error('Promise error');
});
  • 此时只有then的第二个参数可以捕获到错误信息

    1
    2
    3
    4
    5
    6
    7
    promise.then(res => {
    //
    }, err => {
    console.log('then2catch', err); // 【捕获】Promise error
    }).catch(err1 => {
    console.log('catch', err1); // 【不会捕获】
    });
  • 此时catch方法可以捕获到错误信息

    1
    2
    3
    4
    5
    promise.then(res => {
    //
    }).catch(err1 => {
    console.log('catch', err1); // 【捕获】Promise error
    });
  • 此时只有then的第二个参数可以捕获到 Promise 内部抛出的错误信息

    1
    2
    3
    4
    5
    6
    7
    promise.then(res => {
    throw new Error('then1 error');
    }, err => {
    console.log('then2catch', err); // 【捕获】Promise error
    }).catch(err1 => {
    console.log('catch', err1); // 【不会捕获】由于并没有进入到then的第一个参数,也就没有抛出错误。
    });
  • 此时then的第二个参数可以捕获到 Promise 内部抛出的错误信息; catch方法可以捕获到 then的第二个参数 抛出的错误

    1
    2
    3
    4
    5
    6
    7
    8
    promise.then(res => {
    throw new Error('then1 error');
    }, err => {
    console.log('then2catch', err); // 【捕获】Promise error
    throw new Error('then2 error'); // 并抛出错误
    }).catch(err1 => {
    console.log('catch', err1); // 【捕获】then2 error
    });

总结

第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。

因此,建议总是使用catch方法,而不使用then方法的第二个参数。使得异步代码更易于编写、理解和维护

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fetchData = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const data = '这是从服务器获取的数据';
if (data) {
resolve(data); // 异步操作成功
} else {
reject('数据获取失败'); // 异步操作失败
}
}, 1000); // 模拟1秒延迟
});

// 使用 Promise
fetchData.then((data) => {
console.log('成功:', data);
})
.catch((error) => {
console.error('失败:', error);
})
.finally(() => {
console.log("Operation completed.");
});

Promise 相关的静态方法

  1. Promise.all(iterable)

    接受一个可迭代对象(如数组),并返回一个 Promise,该 Promise在所有输入的 Promise 都成功时成功,或者在任何一个输入的 Promise 失败时失败

    • 实际上,Promise.all() 只接受 Promise 对象的可迭代对象作为输入。它的设计是用来处理多个 Promise 对象,并等待它们全部解决或拒绝。

    • 如果传入的数组中包含非 Promise 对象(如数字、字符串等)Promise.all() 会将这些非 Promise视为已解决的Promise。这意味着它们会立即被视为解决状态,并且 Promise.all() 将继续处理这些值。

    1
    2
    3
    Promise.all([promise1, promise2])
    .then(results => { /* 所有 Promise 成功 */ })
    .catch(error => { /* 任何 Promise 失败 */ });

    在如下示例中,Promise.all()等待 promise1promise2promise3 都解决,然后返回一个包含所有结果的数组

    如果将 promise1promise2 改为 rejectresults 为 【Error: Result from promise 2】(因为最先失败的是promise2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Result from promise 1'), 1000);
    });

    const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Result from promise 2'), 500);
    });

    const promise3 = new Promise((resolve, reject) => {
    setTimeout(() => resolve('Result from promise 3'), 1500);
    });

    Promise.all([promise1, promise2, promise3])
    .then(results => {
    console.log(results); // 输出 ['Result from promise 1', 'Result from promise 2', 'Result from promise 3']
    })
    .catch(error => {
    console.error('Error:', error);
    });
  2. Promise.race(iterable)

    接受一个可迭代对象,并返回一个 Promise,该 Promise在第一个输入的 Promise 完成时完成,不论是成功还是失败。其他异步任务仍然执行,但是结果不保留(不会放在成功的回调里)

    1
    2
    3
    Promise.race([promise1, promise2])
    .then(result => { /* 第一个 Promise 完成 */ })
    .catch(error => { /* 第一个 Promise 失败 */ });

    在如下示例中,promise2 完成得更快,所以 Promise.race() 返回的 Promise 将以 ‘Promise 2 resolved’ 解决而 promise1 会继续执行,但结果不会影响最终的 Promise 状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => { console.log('Promise 1'); resolve('Promise 1 resolved'); }, 1000);
    });

    const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => { console.log('Promise 2'); resolve('Promise 2 resolved'); }, 500);
    });

    Promise.race([promise1, promise2])
    .then(result => {
    console.log(result); // 输出 'Promise 2 resolved'
    });
    /** output:
    * Promise 2
    * Promise 2 resolved
    * Promise 1
    */
  3. Promise.resolve(value)

    返回一个 Promise,该 Promise 以给定值 value 完成。如果 value 是一个 Promise,则直接返回这个 Promise

    1
    2
    Promise.resolve("Immediate result")
    .then(result => { console.log(result); }); // 输出 "Immediate result"
  4. Promise.reject(reason)

    返回一个 Promise,该 Promise 以给定的拒绝原因 reason 被拒绝。

    1
    2
    Promise.reject("Error reason")
    .catch(error => { console.error(error); }); // 输出 "Error reason"

面试:Promise.all() 的手动实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function allPromisesFulfilled(p1, p2, p3, f1) {
let fulfilledCount = 0;
const totalPromises = 3;

return new Promise((resolve, reject) => {
function checkCompletion() {
if (fulfilledCount === totalPromises) {
f1();
resolve(); // Optional: resolve the Promise if needed
}
}

// Handle p1
p1.then(() => {
fulfilledCount++;
checkCompletion();
}).catch((err) => reject(err)); // 和.catch(reject) 等效: 两者都会将 p1 被拒绝的原因传递给 reject 函数。

// Handle p2
p2.then(() => {
fulfilledCount++;
checkCompletion();
}).catch(reject);

// Handle p3
p3.then(() => {
fulfilledCount++;
checkCompletion();
}).catch(reject);
});
}

// Example usage
// const p1 = Promise.reject('Result from p1');
const p1 = Promise.resolve('Result from p1');
const p2 = Promise.resolve('Result from p2');
const p3 = Promise.resolve('Result from p3');

function f1() {
console.log('All promises are fulfilled, executing f1.');
}

allPromisesFulfilled(p1, p2, p3, f1)
.then(() => console.log('Operation completed successfully'))
.catch(err => console.error('An error occurred:', err));

asyncawait 关键字【ES8 引入】

  • async 关键字:在函数前加上 async 关键字将函数声明为异步函数。异步函数总是返回一个 Promise。如果函数返回的是一个非 Promise 的值,那么它会被自动包装成一个已解决的 Promise。
  • await 关键字await 只能在 async 函数内部使用。它会暂停异步函数的执行,直到 Promise 解决,并且返回 Promise 的结果(内部包装的值)。如果 Promise 被拒绝(即发生错误),await 表达式会抛出该错误。

优点对比

使用 promises 和显式的 .then().catch()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fetchData()
.then(response => {
// 处理响应
console.log("响应:", response);
return processData(response);
})
.then(result => {
// 处理处理后的数据
console.log("处理后的数据:", result);
})
.catch(error => {
// 处理任何错误
console.error("错误:", error);
});

使用 async / await

1
2
3
4
5
6
7
8
9
10
11
12
async function fetchDataAndProcess() {
try {
const response = await fetchData();
console.log("响应:", response);

const result = await processData(response);
console.log("处理后的数据:", result);
} catch (error) {
console.error("错误:", error);
}
}
fetchDataAndProcess();

通过使用 await,我们可以消除使用 promises 时通常需要的明确的 .then().catch()。相反,我们可以以更线性和类似同步代码的方式构建代码。这使得更容易理解程序的流程并以更简洁的方式处理错误。