1. 基本功能
在处理一些需要花费比较长时间的任务时,使用Promise就可以进行异步的处理,防止阻塞。实现把异步当同步写。
2. 解决问题
解决回调地狱问题,而且因为有了resolve和reject可以进行异步处理并且得知任务进度
3. 异步任务
异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。
1 2 3 4 5 6 7 8 9
| setTimeout(function(){ console.log('我在上面') },1000} console.log('我在下面')
执行结果: 我在下面 我在上面 >
|
这种不阻塞后面任务执行的任务就叫异步任务
4. 回调地狱
上面说到,异步任务不能保证按照顺序执行,但实际上我们是有这样的需求的。在没有promise的时候我们这样子实现。
1 2 3 4 5 6 7 8 9
| setTimeout(function(){ console.log("获取id"); setTimeout(function(){ console.log("获取用户名"); setTimeout(function(){ console.log("获取email"); },2000) },1000) },3000)
|
可以看到,代码中的回调函数套回调函数,居然套了3层,这种回调函数中嵌套回调函数的情况就叫做回调地狱。
总结一下,回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
5. Promise基本使用
new Promise((resolve,reject)=>{})
Promise接受一个函数作为参数
函数参数中接受俩个形参参数
-
控制台打印Promise实例,他有两个属性
6. Promise的状态
第一种状态:pending (准备,待解决,进行中)
第二种状态:fulfilled (已完成,成功)
第三种状态:rejected (已拒绝,失败)
7. Promise状态的改变
通过调用resolve()和reject()改变当前promise对象的状态
示例
1 2 3 4
| const p = new Promise((resolve,reject)=>{ resolve() }) console.log(p)
|
1 2 3 4
| const p = new Promise((resolve,reject)=>{ reject() }) console.log(p)
|
promise状态的改变是一次性的。 改状态只能改一次,不是fulfilled就是rejected
8. Promise的结果属性
通过调用resolve()或reject()传递参数,改变当前promise对象的结果
1 2 3 4
| const p = new Promise((resolve,reject)=>{ resolve('resolve的参数') }) console.log(p)
|
9. Promise的方法
1) then方法
参数是两个回调函数
1 2 3 4 5 6 7 8
| p.then((value)=>{ console.log('成功时调用',value); }, ()=>{ console.log('失败时调用'); })
|
then方法返回值是一个新的Promise实例,状态是pending
promise的状态不改变,不会执行then里的方法
这意味着我们可以进行链式编码
new Promise((resolve,reject)=>{}).then().then().then()
如何修改上一个then返回的promise的状态
使用return 可以将返回的promise的状态修改成fulfilled
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p = new Promise((resolve,reject)=>{ resolve() })
const t = p.then((value)=>{ console.log("成功"); return 123 },()=>{ console.log("失败"); })
t.then((value)=>{ console.log('成功2',value); },()=>{ console.log("失败"); })
|
什么情况会修改promise为reject呢
如果then方法中,出现代码错误,会将返回的promise实例的状态改为fulfilled
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p = new Promise((resolve,reject)=>{ resolve() })
const t = p.then((value)=>{ console.log(a) },()=>{ console.log("失败"); })
t.then((value)=>{ console.log('成功2',value); },()=>{ console.log("失败"); })
|
2) catch方法
当promise状态为rejected或promise执行体中出现代码出错时,将执行catch方法,参数是错误信息
1 2 3 4 5 6 7
| const p = new Promise((resolve,reject)=>{ reject(); }).catch((e)=>{ console.log('失败',e); })
|
3) Promise.all
将多个Promise实例包装成一个新的Promise实例的方法,Promise.all的入参不一定非要是数组,只要是具有Iterator结构的数据集合都行。
Promise.all 等待所有成功返回成功(有一个失败,则进入失败方法)。
应用场景:
- 一个页面,有多个请求,我们需求所有的请求都返回数据后再一起处理渲染
- 我们需求单独处理一个请求的数据渲染和错误处理逻辑,有多个请求,我们就需要在多个地方写
- 验证多个请求结果是否都是满足条件
1 2 3 4 5 6 7 8 9
| var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); });
Promise.all([p1, p2, p3]).then(values => { console.log(values); });
|
手写实现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
|
Promise.myall = function (array) { let result = []; let index = 0; return new Promise((resolve, reject) => { function addData(key, value) { result[key] = value index++ if (index === array.length) { resolve(result) } } for (let i = 0; i < array.length; i++) { let current = array[i]; if (current instanceof Promise) { current.then(value => addData(i, value), reason => reject(reason)) } else { addData(i, array[i]) } } }) }
|
4)Promise存在的问****题
- promise一旦执行,无法中途取消
- promise的错误无法在外部被捕捉到,只能在内部进行预判处理
- promise的内如何执行,监测起来很难
10. promise实践
常见写法
1 2 3 4 5 6 7
| new Promise((resolve,reject)=>{
}).then(value=>{ console.log("成功",value); }).catch(reason=>{ console.log(reason); })
|
解决回调地狱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| new Promise((resolve, reject) => { setTimeout(() => { resolve("第一次请求数据") }, 3000) }).then((data) => { console.log(data);
return new Promise((resolve, reject) => { setTimeout(() => { resolve("第二次请求数据") },1000) }) }).then(data=>{ console.log();
return new Promise((resolve, reject) => { setTimeout(() => { resolve("第三次请求数据") },2000) }) }).then(data=>{ console.log(data); })
|
11. async,await
使用async,await语法糖可以让异步代码看起来像同步代码,提高代码可阅读性。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function getData() { fetch("https://jsonplaceholder.typicode.com/posts/1").then(res => { return res.json() }).then(res => { console.log(res); }).catch(error=>{ console.log(error); }).finally(()=>{ }) }
async function getData(){ const response = await fetch("https://jsonplaceholder.typicode.com/posts/1") const json = await response.json() console.log(json); } getData()
|
使用async,await应该避免的坑:
一、使用async和await会打破并行
1 2 3 4
| async function getData(){ const a = await fetch("https://...") const b = await fetch("https://...") }
|
这里的两个fetch并不是并行操作,而是a先获取到,再执行b的请求。更高效的做法是将所有Promise用Promise.all组合起来,然后再去await。
1 2 3 4 5 6
| async function getData(){ const promiseA = fetch("https://...") const promiseB = fetch("https://...")
const[a,b] = await Promise.all([promiseA,promiseB]) }
|
二、在循环中执行异步操作,不能直接调用forEach或map这一类方法的
如果我们希望等待循环中的异步操作都一一完成之后才继续执行,我们应该使用for循环
1 2 3 4 5 6 7 8
| async function f(){ for (let i of [1,2,3]){ await someAsyncOperation() } console.log("done") } f()
|
更进一步,如果我们需要所有请求并发执行,我们可以使用for await
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| async function f(){ const promises = [ someAsyncOperation(), someAsyncOperation(), someAsyncOperation(), ] for await (let result of promises){ }
console.log("done") } f()
|
三、更简洁的写法
1 2 3 4 5 6 7 8
|
(async()=>{ await someAsyncOperation() })()
|