Promise 从入门到自定义
准备
区别实例对象与函数对象
实例对象: new 函数产生的对象, 称为实例对象, 简称为对象
函数对象: 将函数作为对象使用时, 简称为函数对象
1
2
3
4
5
6function Fn() {} // Fn函数
const fn = new Fn(); // Fn是构造函数 fn是实例对象(简称为对象)
console.log(Fn.prototype); // Fn.属性 Fn是函数对象
Fn.bind({}); // Fn.方法 Fn是函数对象
$("#test"); // $是jquery函数
$.get("/test"); // $是jquery函数对象
回调函数
定义:按照调用者有的约定实现函数的功能,调用者不调用但会执行
同步回调: 立即执行, 完全执行完了才结束, 不会放入回调队列中
- 例子: 数组遍历相关的回调函数
- Promise 的 excutor 函数
异步回调: 不会立即执行, 会放入回调队列中将来执行
- 定时器回调
- ajax 回调
- Promise 的成功|失败的回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<script type="text/javascript">
const arr = [1, 2, 3];
// 立即执行, 完全执行完了才结束, 不会放入回调队列中
arr.forEach((element) => {
console.log(element);
});
console.log("forEach之后");
// 不会立即执行, 会放入回调队列中将来执行
setTimeout(() => {
console.log("回调函数");
}, 0);
console.log("setTimeout之后");
</script>
错误处理
错误的类型
Error: 所有错误的父类型
ReferenceError: 引用的变量不存在
TypeError: 数据类型不正确的错误
RangeError: 数据值不在其所允许的范围内
SyntaxError: 语法错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// ReferenceError: 引用的变量不存在
console.log(a) // ReferenceError: a is not defined
console.log('-----') // 没有捕获error, 下面的代码不会执行
// TypeError: 数据类型不正确的错误
let b
console.log(b.xxx) // TypeError: Cannot read property 'xxx' of undefined
b = {}
b.xxx() // TypeError: b.xxx is not a function
// RangeError: 数据值不在其所允许的范围内
function fn() {
fn()
}
fn() // RangeError: Maximum call stack size exceeded
// SyntaxError: 语法错误
const c = """" // SyntaxError: Unexpected string
错误处理
捕获错误: try … catch
1
2
3
4
5
6
7
8try {
let d;
console.log(d.xxx);
} catch (error) {
console.log(error.message);
console.log(error.stack);
}
console.log("出错之后");抛出错误: throw error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function something() {
if (Date.now() % 2 === 1) {
console.log("当前时间为奇数, 可以执行任务");
} else {
// 如果时间是偶数抛出异常, 由调用来处理
throw new Error("当前时间为偶数无法执行任务");
}
}
// 捕获处理异常
try {
something();
} catch (error) {
alert(error.message);
}
错误对象
- message 属性: 错误相关信息
- stack 属性: 函数调用栈记录信息
Promise 是什么?
抽象表达: Promise是JS中进行异步编程的新的解决方案
具体表达:
- 从语法上来说: Promise是一个构造函数
- 从功能上来说: promise对象用来封装一个异步操作并可以获取其结果
promise的状态改变(只有2种, 只能改变一次)
- pending变为resolved
- pending变为rejected
promise的基本流程
初体验
执行器函数: 同步调用
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<script>
// 1. 创建一个新的promise对象
const p = new Promise((resolve, reject) => {// 执行器函数 同步回调
console.log('执行 excutor')
// 2. 执行异步操作任务
setTimeout(() => {
const time = Date.now() // 如果当前时间是偶数就代表成功, 否则代表失败
// 3.1. 如果成功了, 调用resolve(value)
if (time %2 == 0) {
resolve('成功的数据, time=' + time)
} else {
// 3.2. 如果失败了, 调用reject(reason)
reject('失败的数据, time=' + time)
}
}, 1000);
})
console.log('new Promise()之后')
// setTimeout(() => {
p.then(
value => { // 接收得到成功的value数据 onResolved
console.log('成功的回调', value)
},
reason => {// 接收得到失败的reason数据 onRejected
console.log('失败的回调', reason)
}
// )
// }, 2000);
</script>
为什么要用 Promise?
灵活指定回调函数
指定回调函数的方式更加灵活: 可以在请求发出甚至结束后指定回调函数
旧的: 必须在启动异步任务前指定
1
2
3
4
5
6
7
8
9
10
11
12
13<script>
// 成功的回调函数
function successCallback(result) {
console.log("声音文件创建成功: " + result);
}
// 失败的回调函数
function failureCallback(error) {
console.log("声音文件创建失败: " + error);
}
/* 使用纯回调函数 */
createAudioFileAsync(audioSettings, successCallback, failureCallback)
</script>使用Promise
1
2
3
4
5const promise = createAudioFileAsync(audioSettings); // 假设2秒处理完成
setTimeout(() => {
// 可以在3秒之后再处理
promise.then(successCallback, failureCallback);
}, 3000);
链式调用解决回调地狱
支持链式调用, 可以解决回调地狱问题
回调地狱: 在js里,在异步js里,回调函数写的太多了,回调套回调。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/*
回调地狱
*/
doSomething(function (result) {
doSomethingElse(
result,
function (newResult) {
doThirdThing(
newResult,
function (finalResult) {
console.log("Got the final result: " + finalResult);
},
failureCallback,
);
},
failureCallback,
);
}, failureCallback);使用promise的链式调用解决回调地狱
1
2
3
4
5
6
7
8
9
10
11
12doSomething()
.then(function (result) {
return doSomethingElse(result);
})
// 返回的是一个上一个异步操作的结果
.then(function (newResult) {
return doThirdThing(newResult);
})
.then(function (finalResult) {
console.log("Got the final result: " + finalResult);
})
.catch(failureCallback);async/await: 回调地狱的终极解决方案
1 | async function request() { |
如何使用 Promise
API
1. Promise构造函数: Promise (excutor) {}
excutor函数: 同步执行 (resolve, reject) => {}
resolve函数: 内部定义成功时我们调用的函数 value => {}
reject函数: 内部定义失败时我们调用的函数 reason => {}
说明: excutor会在Promise内部立即同步回调,异步操作在执行器中执行
2. Promise.prototype.then方法: (onResolved, onRejected) => {}
onResolved函数: 成功的回调函数 (value) => {}
onRejected函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功value的成功回调和用于得到失败reason的失败回调
返回一个新的promise对象
3. Promise.prototype.catch方法: (onRejected) => {}
onRejected函数: 失败的回调函数 (reason) => {}
说明: then()的语法糖, 相当于: then(undefined, onRejected)
4. Promise.resolve方法: (value) => {}
value: 成功的数据或promise对象
说明: 返回一个成功/失败的promise对象
5. Promise.reject方法: (reason) => {}
reason: 失败的原因
说明: 返回一个失败的promise对象
6. Promise.all方法: (promises) => {}
promises: 包含n个promise的数组
说明: 返回一个新的promise, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败
7. Promise.race方法: (promises) => {}
promises: 包含n个promise的数组
说明: 返回一个新的promise, 第一个完成的promise的结果状态就是最终的结果状态
Promise构造函数
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('成功的数据')
reject("失败的数据");
}, 1000);
})
.then((value) => {
console.log("onResolved()1", value);
})
.catch((reason) => {
console.log("onRejected()1", reason);
});
resolve & reject方法
创建一个成功的promise对象
1
2
3
4
5
6// 产生一个成功值为1的promise对象
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});使用resolve,reject方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 产生一个成功值为2的promise对象
const p2 = Promise.resolve(2);
// 产生一个失败值为3的promise对象
const p3 = Promise.reject(3);
p1.then((value) => {
console.log(value);
});
p2.then((value) => {
console.log(value);
});
p3.catch((reason) => {
console.log(reason);
});
// 结果文件:
2;
3;
1;
all & race
代码如下:
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
30const p4 = Promise.reject(4)
// 情况一
const pAll = Promise.all([p1, p2, p4, p3])
// 情况二
const pAll = Promise.all([p1, p2])
pAll.then(
values => {
console.log('all onResolved()', values)
},
reason => {
console.log('all onRejected()', reason)
}
)
const pRace = Promise.race([p1, p2, p3])
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
// 第一种情况:有多个错误
all onRejected() 4 // 返回第一个错误的结果
race onRejected() 2 // 返回第一个执行完成的结果,全为rejected状态返回第一个结果
// 第二种情况:全部正确
race onResolved() 2 // 返回第一个执行完成的结果
all onResolved() [1, 2] // 返回成功的数组结果
Promise理解
状态改变
resolve(value): 如果当前是pendding就会变为resolved
reject(reason): 如果当前是pendding就会变为rejected
抛出异常: 如果当前是pendding就会变为rejected
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12const p = new Promise((resolve, reject) => {
// resolve(1) // promise变为resolved成功状态
// reject(2) // promise变为rejected失败状态
// throw new Error('出错了') // 抛出异常, promse变为rejected失败状态, reason为 抛出的error
throw 3; // 抛出异常, promse变为rejected失败状态, reason为 抛出的3
});
p.then(
(value) => {},
(reason) => {
console.log("reason", reason);
},
);
指定多个成功/失败回调函数
当promise改变为对应状态时都会调用
代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const p = new Promise((resolve, reject) => {
// resolve(1)
// reject(2)
// throw new Error('出错了')
throw 3
})
p.then(
value => {},
reason => {console.log('reason', reason)}
)
p.then(
value => {},
reason => {console.log('reason2', reason)}
)
// 结果
eason 3
reason2 3
then()返回的结果状态
简单表达: 由then()指定的回调函数执行的结果决定
详细表达:
- 如果抛出异常, 新promise变为rejected, reason为抛出的异常
- 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值
- 如果返回的是另一个新promise, 此promise的结果就会成为新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
30new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
// return 2
// return Promise.resolve(3)
// return Promise.reject(4)
throw 5
},
reason => {
console.log('onRejected1()', reason)
// return 2
// return Promise.resolve(3)
// return Promise.reject(4)
throw 5
}
).then(
value => {
console.log('onResolved2()', value)
},
reason => {
console.log('onRejected2()', reason)
}
)
// 结果
onRejected1() 1
onRejected2() 5
顺序:状态改变和回调函数
都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
常规:指定回调函数, 后改变的状态
1
2
3
4
5
6
7
8
9
10
11
12
13new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1); // 后改变的状态(同时指定数据), 异步执行回调函数
}, 1000);
}).then(
// 先指定回调函数, 保存当前指定的回调函数
(value) => {
console.log("value", value);
},
(reason) => {
console.log("reason", reason);
},
);如何先改状态, 后指定回调函数?
在执行器中直接调用resolve()/reject()
1
2
3
4
5
6
7
8
9
10
11
12// 如何先改状态, 后指定回调函数
new Promise((resolve, reject) => {
resolve(1) // 先改变的状态(同时指定数据)
}).then(// 后指定回调函数, 异步执行回调函数
value => {console.log('value2', value)},
reason => {console.log('reason2', reason)}
)
console.log('-------')
// 结果
-------
value2 1延迟更长时间才调用then()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1); // 先改变的状态(同时指定数据), 异步执行回调函数
}, 1000);
});
setTimeout(() => {
p.then(
(value) => {
console.log("value3", value);
},
(reason) => {
console.log("reason3", reason);
},
);
}, 1100);
串连多个操作任务
promise的then()返回一个新的promise, 通过then的链式调用串连多个同步/异步任务
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
35new Promise((resolve, reject) => {
setTimeout(() => {
console.log("执行任务1(异步)")
resolve(1)
}, 1000);
}).then(
value => {
console.log('任务1的结果: ', value)
console.log('执行任务2(同步)')
return 2
}
).then(
value => {
console.log('任务2的结果:', value)
return new Promise((resolve, reject) => {
// 启动任务3(异步)
setTimeout(() => {
console.log('执行任务3(异步))')
resolve(3)
}, 1000);
})
}
).then(
value => {
console.log('任务3的结果: ', value)
}
// 结果
执行任务1(异步)
任务1的结果: 1
执行任务2(同步)
任务2的结果: 2
执行任务3(异步))
任务3的结果: 3
promise异常穿透
当使用promise的then链式调用时, 可以在最后指定失败的回调,前面任何操作出了异常, 都会传到最后失败的回调中处理。
如下面代码,执行器中是reject(1)执行的是then中reject方法,没有定义,会往下查找,最后执行到catch代码块中。
默认不写reject的处理,可以理解这里有二种写法
- 返回reject的promise
- 抛除异常,返回reject的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
26new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
},
// reason => {throw reason}
).then(
value => {
console.log('onResolved2()', value)
return 3
},
reason => {throw reason}
).then(
value => {
console.log('onResolved3()', value)
},
reason => Promise.reject(reason)
).catch(reason => {
console.log('onReejected1()', reason)
})
// 结果
onReejected1() 1
中断promise链
如果在catch后面添加.then后还是会急需执行
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
27new Promise((resolve, reject) => {
// resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
},
// reason => {throw reason}
).then(
value => {
console.log('onResolved2()', value)
return 3
},
reason => {throw reason}
).catch(reason => {
console.log('onReejected1()', reason)
}).then(
value => {
console.log('onResolved3()', value)
},
reason => Promise.reject(reason)
)
// 结果
onReejected1() 1 // 失败后的处理
onResolved3() undefined // 在catch中没有返回值,默认是return undefined
async 与 await
语法
asyn函数
- 函数的返回值为promise对象
- promise对象的结果由async函数执行的返回值决定
await 表达式
- await右侧的表达式一般为promise对象, 但也可以是其它的值
- 如果表达式是promise对象, await返回的是promise成功的值
- 如果表达式是其它值, 直接将此值作为await的返回值
注意:
- await必须写在async函数中, 但async函数中可以没有await
- 如果await的promise失败了, 就会抛出异常, 需要通过try…catch来捕获处理
async函数示例
代码如下:
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
43async function fn1() {
return 1
// throw 2
// return Promise.reject(3)
// return Promise.resolve(3)
/* return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(4)
}, 1000);
}) */
}
const result = fn1()
console.log(result)
// 结果
Promise {<fulfilled>: 1}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 1
// -------------------------
Promise {<rejected>: 2}
__proto__: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: 2
Uncaught (in promise) 2
// -------------------------
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: 3
Uncaught (in promise) 3
// -------------------------
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 3
// -------------------------
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 4
await表达式示例
代码如下:
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
26function fn2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(5)
reject(6)
}, 1000);
})
}
async function fn3() {
try {
const value = await fn2() // await右侧表达为promise, 得到的结果就是promise成功的value
console.log('value', value)
} catch (error) {
// 如果await的promise失败了, 就会抛出异常, 需要通过try...catch来捕获处理
console.log('得到失败的结果', error)
}
fn3()
// 结果
value 5 // resolve(5)
得到失败的结果 6 // reject(6)
// 同时放开 resolve(5) reject(6)
value 5 // 一个promise只能改变一次状态await表达式右侧不是promise
1
2
3
4
5
6
7
8
9
10
11
12
13function fn4() {
return 6
}
async function fn3() {
const value = await fn4() // await右侧表达不是promise, 得到的结果就是它本身
console.log('value', value)
}
fn3()
// 结果
value 6
JS 异步之宏队列与微队列
执行顺序
- 宏列队: 用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM事件回调/ajax回调
- 微列队: 用来保存待执行的微任务(回调), 比如: promise的回调/MutationObserver的回调
- JS执行时会区别这2个队列
- JS引擎首先必须先执行所有的初始化同步任务代码
- 每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行
练手1
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<script>
setTimeout(()=>
{
console.log(1) // 第一个宏任务
}
,0) Promise.resolve().then(()=>
{
console.log(2) // 第一个微任务
}
) Promise.resolve().then(()=>
{
console.log(4) // 第二个微任务
}
) console.log(3) // 同步任务先输出
</script>结果: 3 2 4 1
练手2
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<script>
setTimeout(() => {
console.log(1) // 第一个宏任务
}, 0)
new Promise((resolve) => {
console.log(2) // 同步执行
resolve()
}).then(() => {
console.log(3) // 第一个微任务
}).then(() => {
console.log(4) // 第二个微任务
})
console.log(5) // 同步任务先输出
</script>结果: 2 5 3 4 1
练手3
代码如下:
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<script>
// first函数 ,返回的是一个promise对象,结果为 resolve(2)
const first = () => (new Promise((resolve, reject) => {
// 同步代码优先输出
console.log(3)
// ---- start -----定义 Promise 实例对象 p。
let p = new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(5)
resolve(6)
}, 0)
resolve(1)
})
// ---- end -----
resolve(2)
p.then((arg) => {
console.log(arg)
})
}))
first().then((arg) => {
console.log(arg)
})
console.log(4)
</script>结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
定义了first函数 ,返回的是一个promise对象,结果为 resolve(2)
1. first() 执行函数
执行同步代码 输出3 ,
2. 执行定义的promise的excute ,输出7
定时器任务放到宏队列里,p的结果为reslove(1),then的代码放入微队列里,数据为1
宏: [5]
微: [1]
3. first().then的输出(数据为2)放到第二个微队列里, 先执行最后console的同步任务,输出 4
宏: [5]
微: [1, 2]
4. 先执行微队列里的 后执行宏队列 输出 1 2 5 p的结果不可更改
结果为: 3 7 4 1 2 5
*/
练手4
代码如下:
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<script>
setTimeout(() => {
console.log("0")
}, 0)
new Promise((resolve,reject)=>{
console.log("1")
resolve()
}).then(()=>{
console.log("2")
new Promise((resolve,reject)=>{
console.log("3")
resolve()
}).then(()=>{
console.log("4")
}).then(()=>{
console.log("5")
})
}).then(()=>{
console.log("6")
})
new Promise((resolve,reject)=>{
console.log("7")
resolve()
}).then(()=>{
console.log("8")
})
</script>结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
1. 查看同步代码 有7 24 行先输出 1 7
2. 定时器添加到宏任务,继续执行第一个promise的代码,将then的第一个异步操作放入微队列
继续向下执行,将第二个promise的then异步操作放到微队列
宏: [0 ]
微: [2 8]
3. 执行第一个微队列,根据8行的结果进入then,打印 2,创建promise对象,执行器打印 3,将打印4放入到微任务
微: [8 4]
4. 由于p1的then执行完成,将20行加入微队列
微: [8 4 6 ]
5. 继续执行微队列,打印 8
微: [4 6 ]
6. 继续执行微队列,打印 4,执行同步的then操作,将17行异步放入到微队列
微: [6 5]
7. 先微队列后宏队列,依次输出 6 5 0
结果: 1 7 2 3 8 4 6 5 0
*/