Promise

Promise

Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个未来才会完成(或失败)的操作的结果。可以把它理解为“承诺”:现在无法获得结果,但将来一定会告知成功还是失败。

1-介绍

理解

1.抽象表达:

  • Promise是一门新的技术(ES6规范)
  • Promise 是 JS 中进行异步编程的新解决方案

备注:旧方案是单纯使用回调函数

2.具体表达:

  • 从语法上来说:Promise是一个构造函数
  • 从功能上来说:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值

优势

指定回调函数的方式更加灵活

  • 旧的:必须在启动异步任务前指定
  • promise:启动异步任务=>返回promie对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)

支持链式调用,解决回调地狱的问题

2-基本使用

Promise的状态PromiseState

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 成功(fulfilled): 意味着操作成功完成。
  • 失败(rejected): 意味着操作失败。

一个 Promise 的状态只能由 pending 变为 fulfilled,或者变为 rejected,且只能改变一次。

链式调用:Promise 对象具有 thencatch 方法来处理下一步的操作。因为 Promise.prototype.thenPromise.prototype.catch 方法返回的是 Promise,所以它们可以被链式调用。

链式调用例子:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
// 模拟异步请求函数(返回 Promise)
function fetchUser(userId) {
return new Promise((resolve, reject) => {
console.log('正在获取用户信息...');
setTimeout(() => {
if (userId === 123) {
resolve({ id: 123, name: '张三' });
} else {
reject(new Error('用户不存在'));
}
}, 1000);
});
}

function fetchOrders(userId) {
return new Promise((resolve) => {
console.log(`正在获取用户 ${userId} 的订单...`);
setTimeout(() => {
resolve([
{ id: 1, amount: 100 },
{ id: 2, amount: 250 },
{ id: 3, amount: 80 }
]);
}, 800);
});
}

function calculateTotal(orders) {
return new Promise((resolve) => {
console.log('正在计算订单总金额...');
const total = orders.reduce((sum, order) => sum + order.amount, 0);
setTimeout(() => resolve(total), 500);
});
}

// ===== 链式调用 =====
fetchUser(123)
.then(user => {
console.log('用户:', user.name);
return fetchOrders(user.id); // 返回下一个 Promise
})
.then(orders => {
console.log('订单数量:', orders.length);
return calculateTotal(orders); // 继续返回 Promise
})
.then(total => {
console.log('总金额:', total, '元');
})
.catch(error => {
console.error('出错了:', error.message);
});

// 输出顺序(约 2.3 秒内完成):
// 正在获取用户信息...
// 用户: 张三
// 正在获取用户 123 的订单...
// 订单数量: 3
// 正在计算订单总金额...
// 总金额: 430 元

Promise对象的值PromiseResult

保存着对象『成功/失败』的结果,通过resolve()reject()两个函数来修改,保存的值在then()方法中的回调函数中取出

1
2
3
4
5
6
7
8
9
10
11
12
13
const p = new Promise((resolve, reject) => {
if (condition) {
resolve(data)
} else {
reject(err)
}
})

p.then((data) => {
console.log(data);
},(err) => {
console.log(err);
})

Promise的基本流程

流程

应用

封装读取文件:

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
const fs = require('fs')


// 原生
fs.readFile('./test.txt',(err,data) =>{
if(err){
throw err

}else{
console.log(data.toString());
}
})

// promise封装

const p = new Promise((resolve,reject) => {
fs.readFile('./test.txt',(err,data) =>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})

p.then(value => {
console.log(value);
},reason => {
console.log(reason);
})

封装ajax请求:

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
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
<style>
#result {
width: 600px;
height: 300px;
border: 1px solid black
}
</style>
</head>

<body>
<button>点我发送请求</button>
<div id="result"></div>
</body>
<script>
let btn = document.querySelector('button')
let result = document.querySelector('#result')

btn.onclick = function () {

const p = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()

xhr.open('GET', "https://jsonplaceholder.typicode.com/users/k")

xhr.send()

xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status == 200) {
resolve(xhr.response)
}else{
reject(xhr.status)
}
}
}
})

p.then((value)=>{
result.innerHTML = value
},(reason)=>{
console.log(reason);
})
}
</script>

</html>

3-详细使用

Promise API

Promise 构造函数

Promise 构造函数接收一个执行器 executor,来启动这个 Promise。

1
new Promise(executor)

参数 :

resolvereject,分别表示操作成功和操作失败。

1
2
3
4
new Promise((resolve, reject) => {
resolve()
// reject()
})
  • executor 函数:执行器 (resolve, reject) => { }
  • resolve 函数:内部定义成功时调用的函数 value => {}
  • reject 函数:内部定义失败时调用的函数 reason => {}

返回值:

当通过 new 关键字调用 Promise 构造函数时,它会返回一个 Promise 对象。当 resolveFunc 或者 rejectFunc 被调用时,该 Promise 对象就会变为已解决(resolved)。

注意:executor会在Promise内部立即同步调用.异步操作在执行器中执行

  • new Promise(executor) 这行代码执行时,**executor 会立刻运行**(不是等到后面 .then() 被调用才运行)。
  • executor同步执行的,但它的内部可以启动异步操作(比如 setTimeoutfetchfs.readFile 等)。
  • 异步操作的结果通过调用 resolve(value)reject(reason) 来通知 Promise 状态变更。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log('1. 开始');

const promise = new Promise((resolve, reject) => {
console.log('2. executor 同步执行中');

// 模拟异步操作(比如网络请求)
setTimeout(() => {
console.log('4. 异步操作完成,调用 resolve');
resolve('成功数据');
}, 1000);

console.log('3. executor 执行完毕(但异步还没完成)');
});

console.log('5. Promise 对象已创建');

promise.then(result => {
console.log('6. then 回调:', result);
});

执行结果:

1
2
3
4
5
6
7
1. 开始
2. executor 同步执行中
3. executor 执行完毕(但异步还没完成)
5. Promise 对象已创建
(等待 1 秒)
4. 异步操作完成,调用 resolve
6. then 回调: 成功数据
  • 同步部分Promise 构造函数的调用、executor 的初始执行、.then() 方法的调用。
  • 异步部分
    • executor 中启动的异步操作(如网络请求、定时器等);
    • .then() / .catch() 中回调函数的实际执行(总是异步,走微任务队列)。

Promise.prototype.then

Promise.prototype.then 方该方法返回的结果依然是一个 Promise 对象,意味着我们可以进行链式调用。

语法:

1
2
3
4
5
6
7
p.then(onFulfilled, onRejected);

p.then(value => {
// fulfillment
}, reason => {
// rejection
});

参数:

  • onFulfilled 函数:成功的回调函数 (value) => {}
  • onRejected 函数:失败的回调函数 (reason) => {}

返回值:
如果 then 中的回调函数:

  • 返回了一个值,那么 then 返回的 Promise 将会成为接受状态(fulfilled),并且将返回的值作为接受状态的回调函数的参数值。
  • 没有返回任何值,那么 then 返回的 Promise 将会成为接受状态(fulfilled),并且该接受状态的回调函数的参数值为 undefined。
  • 抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态(rejected),并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 返回一个已经是接受状态(fulfilled)的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
  • 返回一个已经是拒绝状态(rejected)的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
  • 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

Promise.prototype.catch()

catch() 方法返回一个 Promise,并且处理操作失败的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。但是用起来更方便了,可以看作是一种 语法糖(让我们执行某些操作更加方便简洁的方法)。

语法:

1
2
3
4
5
p.catch(onRejected);

p.catch(reason => {
// 操作失败
});

等同于:

1
2
3
4
5
p.then(undefined, onFulfilled);

p.then(undefined, reason => {
// 操作失败
})

参数:

  • onRejected:操作失败时的回调函数 (reason) => {}

返回值:

  • 返回一个 Promise 对象。如果 onRejected 抛出一个错误或返回一个本身失败的 Promise,那么 catch() 返回的 Promise 的状态为 rejected;否则,状态为 fulfilled

Promise.resolve()

Promise.resolve() 是一个函数对象方法,意味着只能用在函数对象 Promise 上。它接收一个 value 参数,返回一个这个给定值解析后的 Promise 对象。

语法:

1
Promise.resolve(value);

参数:

  • value:被解析的参数,可以是一个值,也可以是一个 Promise 对象,或者是一个 thenable

返回结果:

  • 若给定值为一个普通数、字符串或数组等等之类的,则返回一个成功的 Promise
  • 若参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象。
  • 由此可知,resolve() 方法可以返回一个成功或者失败的 Promise 对象

Promise.reject()

Promise.reject() 方法返回一个失败的 Promise 对象,接收一个 value 参数,表示失败的原因。

语法:

1
Promise.reject(reason);

参数:

  • reason:表示操作失败的原因

返回值:

  • 返回一个失败(状态为 rejected )的 Promise 对象

Promise.all()

Promise.all() 方法接收由多个 promise 组成的可迭代类型(Array、Map、Set等),然后返回一个新的 promise。它等待所有 promise 完成,或者第一个失败的 promise。

1
2
3
4
5
6
Promise.all(iterable);

Promise.all(promise1, promise2, ...)

const p = Promise(promise1, promise2)
p.then(values=>)

参数:

  • iterable:可迭代类型,一般为数组

返回值:

  • 若传入的参数为空的可迭代对象,例如 Promise.all([]),则返回一个已完成状态(fulfilled)的 Promise
  • 若传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved)的 Promise。例如 Promise.all([1, 2])
  • 其它情况下返回一个处理中(pending)的 Promise。只有接收的参数的 promise 全部成功,这个返回的 Promise 才会是成功的,只要有一个失败,则返回一个失败的 Promise,且返回的值为第一个失败的值。
  • 若所有 promise 都成功,则返回的 Promise 的值为一个包含了多个结果的数组

Promise.all 的同步与异步:

  • 当如果传入的可迭代对象是空的,就是同步;其他情况都是异步

举例:
参数为非空的数组,异步的:

1
2
3
4
5
6
7
8
9
10
const p = Promise.all([1])
setTimeout(() => {
console.log(p)
})
console.log('同步代码')
console.log(p) // (1)
// 输出:
// 同步代码
// Promise {<pending>}
// Promise {<fulfilled>: Array(1)}

注意:此时(1)输出状态为 pending,因为同步代码先执行,而此时异步 promise 的结果还未知

参数为空数组,同步的:

1
2
3
4
5
6
7
8
9
10
const p1 = Promise.all([])
setTimeout(() => {
console.log(p1)
})
console.log('同步代码')
console.log(p1) // (1)
// 输出
// 同步代码
// Promise {<fulfilled>: Array(0)}
// Promise {<fulfilled>: Array(0)}

注意:此时(1)输出状态为 fulfilled,说明此时 Promise.all 是同步的。

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
// 参数为空的数组
const p1 = Promise.all([])

// 参数为不包含 promise 的数组
const p2 = Promise.all([1, 2, 3])

// 参数为全部是成功的 promise 的数组
let pm1 = Promise.resolve(1)
let pm2 = Promise.resolve(2)
let pm3 = Promise.resolve(3)
const p3 = Promise.all([pm1, pm2, pm3])

// 结果
setTimeout(() => {
console.log(p1)
console.log(p2)
console.log(p3)
// Promise {<fulfilled>: Array(0)}
// Promise {<fulfilled>: Array(3)}
// Promise {<fulfilled>: Array(3)}
})

// 参数为包含失败的 promise 的数组
let pe1 = Promise.resolve(1)
let pe2 = Promise.reject('error')
let pe3 = Promise.resolve(3)
const p4 = Promise.all([pe1, pe2, pe3])

setTimeout(() => {
console.log(p4)
})
// 输出
// Promise {<rejected>: 'error'}

Promise.race()

Promise.race() 也是接收一个可迭代类型,例如一个包含了多个 promise 的数组。然后返回一个新的 promise,第一个完成的 promise 的结果状态就是最终返回的结果状态。

需要注意,这里最终返回的结果的状态不一定是参数数组中位于前面的 promise,具体要看哪一个最先完成,所以 race() 方法的返回结果可能为成功或失败的。

语法:

1
Promise.race(iterable);

参数:

  • iterable:可迭代对象,一般为包含多个 promise 的数组

返回值:

  • 返回一个 promise,只要给定的可迭代对象中的 promise 有一个先完成(成功或失败),就采用它的值作为最终 promise 的值,状态与这个完成的 promise 相同。

Promise.race 的异步性(没有同步性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const p = Promise.race([Promise.resolve(10), Promise.resolve(20)])

// 立即输出p,此时p状态为 pending
console.log(p)

setTimeout(() => {
// 等栈中代码执行完毕之后执行,可得到完成的promise
console.log('the stack is now empty');
console.log(p)
})

// 输出:
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: 33 }

注意:Promise.race 没有同步性

Promise的几个关键问题

1-如何修改对象的状态

  • resolve(value):如果当前是 pending 就会变为 resolved
  • reject(reason):如果当前是 pending 就会变为 rejected
  • 抛出异常:如果当前是 pending 就会变为 rejected

2-一个promise指定多个成功/失败回调函数,都会调用吗?

当 promise 改变为对应状态时都会调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let p = new Promise((resolve, reject) => {
throw 100
})
p.then(
value => { },
reason => {
console.log('reason1 ' + reason)
}
)
p.then(
value => { },
reason => {
console.log('reason2 ' + reason)
}
)
// 输出
// reason1 100
// reason2 100

3-改变promise状态和指定回调函数谁先谁后?

  1. 都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调
  2. 如何先改状态再指定回调?
    • 在执行器中直接调用 resolve()/reject()
    • 延迟更长时间才调用 then()
  3. 什么时候才能得到数据?
    • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
    • 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 正常情况,先指定回调再改变状态
const p2 = new Promise((resolve, reject) => {
// setTimeout 第三个参数,该参数将传入第一个函数参数
setTimeout(resolve, 1000, '成功的数据') // 后改变数据,同时传入值
}).then(
// 先指定回调函数,保存当前回调函数
value => {
console.log(value)
}
)

// 先改状态,在指定回调函数
const p1 = new Promise((resolve, reject) => {
resolve(100) // 先改变数据,同时传入值
}).then(
// 后指定回调函数,异步执行回调函数
value => {
console.log(value)
}
)

4-promise.then() 返回的新promise 的结果状态由什么决定?

  • 简单表达:由 then() 指定的回调函数执行的结果决定
  • 详细表达:
    1. 如果抛出异常,新 promise 变为 rejected, reason为抛出的异常
    2. 如果返回的是非 promise 的任意值,新promise变为 resolved,value 为返回的值
    3. 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果

5-promise 如何串连多个操作任务?

  • promise的 then() 返回一个新的 promise,可以看成 then() 的链式调用
  • 通过 then链式调用串连多个同步/异步任务

.then() 中可以处理同步任务,也可以处理异步任务。处理异步任务时需要新创建一个 Promise

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
// 任务1
const p = new Promise((resolve, reject) => {
setTimeout(resolve, 0, '任务1的数据(异步)')
})

p.then(
value => {
console.log(value)
// 任务2(同步)
return '任务2的数据(同步)'
},
reason => {
console.log(reason)
},
).then(
value => {
// 任务3(异步)
return new Promise((resolve, reject) => {
console.log(value)
setTimeout(reject, 0, '任务3的错误信息(异步)')
})
},
reason => {
console.log(reason)
}
).catch(reason => {
console.log(reason)
})
// 输出
// 任务1的数据(异步)
// 任务2的数据(同步)
// 任务3的错误信息(异步)

6-promise异常传透?

在 Promise 的链式调用链中,当所有的 .then 中都没有指定错误处理的回调,则前面出现的异常会在最后失败的回调中处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
new Promise((resolve, reject) => {
reject(1)
}).then(
value => {
console.log(value)
}
).then(
value => {
console.log(value)
}
).catch(reason => {
console.log('异常:' + reason)
})

注意:

  • .catch 所谓的异常穿透并不是一次失败状态就触发 catch,而是一层一层的传递下来的
  • 异常穿透的前提条件是所有的 .then 都没有指定失败状态的回调函数。
  • 如果 .catch 前的所有 .then 都指定了失败状态的回调函数,.catch 就失去了意义。

7-值传透

值穿透指的是,链式调用的参数不是函数时,会发生值穿透,就传入的非函数值忽略,传入的是之前的函数参数。

1
2
3
4
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log) // 1Copy to clipboardErrorCopied

只有传入的是函数才会传递给下一个链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
Promise.resolve(1)
.then(2)
.then(() => 3)
.then(4)
.then(console.log) // 3Copy to clipboardErrorCopied
Promise.resolve(1)
.then(function () {
return 2
})
.then(() => { Promise.resolve(3) }) // 没有返回值,默认 undefined
.then(4)
.then(console.log) // undefined

8-中断Promise链

  • 什么是中断:当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
  • 中断的方法:在回调函数中返回一个 pendding 状态的 promise 对象

如何返回一个 pendding 状态的 promise 对象:

1
return new Promise(() => { }) // pendingCopy to clipboardErrorCopied

或者使用 Promise.race([])race 方法接收一个空的可迭代对象时,该 Promise 会一直处于 pendding 状态。

1
return Promise.race([]) // pendingCopy to clipboardErrorCopied

中断之前:

1
2
3
4
5
6
Promise.resolve(1)
.then(value => {
console.log(value) // 1
}) // 下面都会打印
.then(value => console.log(value)) // undefined
.then(value => console.log(value)) // undefinedCopy to clipboardErrorCopied

中断之后:

1
2
3
4
5
6
7
8
9
Promise.resolve(1)
.then(value => {
console.log(value) // 1
// 返回一个 pending 状态的 promise 来中断这个调用链
return new Promise(() => { })
// 或者:return Promise.race([])
}) // 下面都不会打印
.then(value => console.log(value))
.then(value => console.log(value))

4-手写Promise

这个先空着面试的时候再看

5- async await

async 函数

1
2
3
4
async function main() {
1 - 函数的返回值为 promise 对象
2 - promise 对象的结果由 async 函数执行的返回值决定
}

await表达式

  • await 右侧的表达式一般为 promise 对象,但也可以是其它的值
  • 如果表达式是 promise 对象,await 返回的是 promise 成功的值
  • 如果表达式是其它值,直接将此值作为 await 的返回值
  • async/await 中真正起作用的是 await,可以把 async 关键字简单的当作一个 标识符. 因为如果 异步函数 中不使用 await 关键字,其执行基本上跟普通函数没有什么区别,但是对于 异步函数 的返回值还是会和 普通函数 有区别

注意

  • await 必须写在 async 函数中,但 async 函数中可以没有 await
  • 如果 awaitpromise 失败了,就会抛出异常,需要通过 try..catch 捕获处理
  • 当一个 promise 抛出异常时,要使用 try...catch 捕获异常,必须使用 await 使回调函数变为异步回调,否则 try...catch 无法捕获到错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 正确示例
async function fn1() {
return new Promise((resolve, reject) => {
reject('哈哈哈')
}, 1000)
}
async function fn() {
try {
const res = await fn1() // 将 await 去掉,则catch 无法捕获异常
console.log('成功:' + res)
} catch (error) {
console.log('异常:' + error)
}
}
fn()
// 输出:'异常:哈哈哈'Copy to clipboardErrorCopied
  • async function 中是否有 await 是不一样的。若 async function 中没有 await,则这个函数就是纯同步执行,只是返回值是一个 Promise
  • async function 中添加了 await 后,此行代码之前为立即执行,此行代码之后的语句一起作为 整体 变为微队列中的异步回调。相当于给后面的代码套了一层 then 方法。
  • 在使用 try..catch 捕获了 await 中的异常后,await 后面代码将不会再执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 不使用 await
async function async1() {
let a = "aaa";
console.log(a);
}
async1();
console.log('bbb')
// 先输出 aaa,后输出 bbbCopy to clipboardErrorCopied
// 使用 await
async function async1() {
let a = await "aaa";
console.log(a);
}
async1();
console.log('bbb')
// 先输出 bbb,后输出 aaa