Promise
Promise是什么
1.抽象表达:
(1).Promise是一门新的技术(ES6提出的)
(2).Promise是JS中异步编程的新方案(旧方案是谁?---纯回调)
2.具体表达:
(1).从语法上来说: Promise是一个内置构造函数
(2).从功能上来说: Promise的实例对象可以用来封装一个异步操作,并可以获取其成功/失败的值
1.Promise不是回调,是一个内置的构造函数,是程序员自己new调用的。
• 2.new Promise的时候,要传入一个回调函数,它是同步的回调,会立即在主线程上执行,它被称为executor函数
• 3.每一个Promise实例都有3种状态:初始化(pending)、成功(fulfilled)、失败(rejected)
• 4.每一个Promise实例在刚被new出来的那一刻,状态都是初始化(pending)
• 5.executor函数会接收到2个参数,它们都是函数,分别用形参:resolve、reject接收
• 1.调用resolve函数会:
• (1).让Promise实例状态变为成功(fulfilled)
• (2).可以指定成功的value。
• 2.调用reject函数会:
• (1).让Promise实例状态变为失败(rejected)
• (2).可以指定失败的reason。
基本使用
重要语法
new Promise(executor)构造函数
Promise.prototype.then方法
基本编码流程
1.创建Promise的实例对象(pending状态), 传入executor函数
2.在executor中启动异步任务(定时器、ajax请求)
3.根据异步任务的结果,做不同处理:
3.1 如果异步任务成功了:
我们调用resolve(value), 让Promise实例对象状态变为成功(fulfilled),同时指定成功的value
3.2 如果异步任务失败了:
我们调用reject(reason), 让Promise实例对象状态变为失败(rejected),同时指定失败的reason
4.通过then方法为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason
注意:then方法所指定的:成功的回调、失败的回调,都是异步的回调。
关于状态的注意点
1.三个状态:
pending: 未确定的------初始状态
fulfilled: 成功的------调用resolve()后的状态
rejected: 失败的-------调用reject()后的状态
2.两种状态改变
pending ==> fulfilled
pending ==> rejected
3.状态只能改变一次!!
4.一个promise指定多个成功/失败回调函数, 都会调用吗?
<script type="text/javascript" >
const p = new Promise((resolve,reject)=>{
//模拟一个异步任务
/* setTimeout(()=>{
resolve('我是成功的数据')
},2000) */
//真正开启一个异步任务
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
//readyState为4代表接收完毕,接收的可能是:服务器返回的成功数据、服务器返回的错误
if(xhr.status === 200) resolve(xhr.response)
else reject('请求出错')
}
}
xhr.open('GET','https://api.apiopen.top/getJoke')
xhr.responseType = 'json'
xhr.send()
})
p.then(
(value)=>{console.log('成功了1',value);}, //成功的回调-异步
(reason)=>{console.log('失败了1',reason);} //失败的回调-异步
)
console.log('@');
</script>
封装ajax的GET
/*
定义一个sendAjax函数,对xhr的get请求进行封装:
1.该函数接收两个参数:url(请求地址)、data(参数对象)
2.该函数返回一个Promise实例
(1).若ajax请求成功,则Promise实例成功,成功的value是返回的数据。
(2).若ajax请求失败,则Promise实例失败,失败的reason是错误提示。
*/
function sendAjax(url,data){
return new Promise((resolve,reject)=>{
//实例xhr
const xhr = new XMLHttpRequest()
//绑定监听
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300) resolve(xhr.response);
else reject('请求出了点问题');
}
}
//整理参数
let str = ''
for (let key in data){
str += `${key}=${data[key]}&`
}
str = str.slice(0,-1)
xhr.open('GET',url+'?'+str)
xhr.responseType = 'json'
xhr.send()
})
}
const x = sendAjax('https://api.apiopen.top/getJoke',{page:1,count:2,type:'video'})
x.then(
(data)=>{console.log('成功了',data);},
(reason)=>{console.log('失败了',reason);}
)
Promise的API
new promise()
Promise构造函数: new Promise (executor) {} executor函数: 是同步执行的,(resolve, reject) => {} resolve函数: 调用resolve将Promise实例内部状态改为成功(fulfilled)。 reject函数: 调用reject将Promise实例内部状态改为失败(rejected)。 说明: excutor函数会在Promise内部立即同步调用,异步代码放在excutor函数中。
静态方法
1. Promise.resolve()
1. Promise.resolve方法: Promise.resolve(value)
说明: 用于快速返回一个状态为fulfilled或rejected的Promise实例对象
备注:value的值可能是:(1)非Promise值 (2)Promise值
resolve传入的值: 1. 非Promise值,成功的状态
2. Promise值,状态与值都保持一致, p1,p2指向同一个promise实例
3. thenable对象, 调用then()方法后的状态 就是 新的Promise实例的状态
1.
const p2 = Promise.resolve(100)
p2.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
//成功了 100
2.如果它正在解决一个原生的 Promise,它将返回同一 Promise 实例
const p1 = Promise.reject(-100)
const p2 = Promise.resolve(p1)
p2.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 失败了 -100
3.const p2 = Promise.resolve({
then(resolve, reject){
reject(-100)
}
})
p2.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 失败了 -100
2. Promise.reject()
1. Promise.reject方法: Promise.reject方法(reason)
说明: 用于快速返回一个状态必为rejected的Promise实例对象
reason: 该 Promise 对象被拒绝的原因。
Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。
1.非Promise值
const p2 = Promise.reject(-100)
p2.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 失败了 -100
2. Promise值: 将p1看做普通值,失败的原因就是p1
const p1 = Promise.reject('failure')
const p2 = Promise.reject(p1)
p2.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 失败了 Promise {<rejected>: 'failure'}
3. Promise.all()
1. Promise.all方法: Promise.all(promiseArr)
promiseArr: 包含n个Promise实例的数组
说明: 返回一个新的Promise实例, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败。
Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都成功时,返回的 Promise 也将成功(即使传入的是一个空的可迭代对象),并返回一个包含所有成功值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
1. 所有promise都成功,才成功
const p1 = Promise.resolve('ok')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 500)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const p = Promise.all([p1, p2, p3])
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 成功了 (3) ['ok', '成功了', 'success']
2. 返回最先失败的原因
const p1 = Promise.resolve('ok')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 500)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('faliure')
}, 1000)
})
const p = Promise.all([p1, p2, p3])
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
//失败了 faliure
4. Promise.allSettled()
Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 实例状态为成功,并带有描述每个 Promise 结果的对象数组。
const p1 = Promise.resolve('ok')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 500)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('faliure')
}, 1000)
})
const p = Promise.allSettled([p1, p2, p3])
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 成功了 (3) [{…}, {…}, {…}]
0: {status: 'fulfilled', value: 'ok'}
1: {status: 'fulfilled', value: '成功了'}
2: {status: 'rejected', reason: 'faliure'}
5. Promise.race()
1. Promise.race方法: Promise.race(promiseArr)
promiseArr: 包含n个Promise实例的数组
说明: 返回一个新的Promise实例, 成功还是很失败?以最先出结果的promise为准。
Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。
const p1 = Promise.resolve('ok')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 500)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('faliure')
}, 1000)
})
const p = Promise.race([p1, p2, p3])
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
//成功了 ok
6. Promise.any()
Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,则返回一个拒绝的原因
1. 任一实例成功,则新的promise为成功,返回第一个成功的值
const p1 = Promise.reject('no OK')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功了')
}, 500)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('faliure')
}, 1000)
})
const p = Promise.any([p1, p2, p3])
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 成功了 成功了
2. 所有实例都失败,返回错误信息
const p1 = Promise.reject('no OK')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败了')
}, 500)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('faliure')
}, 1000)
})
const p = Promise.any([p1, p2, p3])
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
//失败了 AggregateError: All promises were rejected
实例方法
Promise.prototype.then()
1. Promise.prototype.then方法: Promise实例.then(onFulfilled,onRejected)
onFulfilled: 成功的回调函数 (value) => {}
onRejected: 失败的回调函数 (reason) => {}
特别注意(难点):then方法会返回一个新的Promise实例对象
const p = new Promise((reslove, reject) => {
setTimeout(() => {
reslove('ok')
}, 1000)
})
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
)
// 成功了 ok
Promise.prototype.catch()
1. Promise.prototype.catch方法: Promise实例.catch(onRejected)
onRejected: 失败的回调函数 (reason) => {}
说明: catch方法是then方法的语法糖, 相当于: then(undefined, onRejected)
// Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。
const p = new Promise((reslove, reject) => {
setTimeout(() => {
reject('-100')
}, 1000)
})
p.then(
undefined,
(reason) => { console.log('失败了', reason) }
)
p.catch(
(reason) => { console.log('失败了', reason) }
)
// 失败了 -100
// 失败了 -100
// undefined 知识占位,底层做了封装, 不会报错
Promise.prototype.finally()
Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。
它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法。
表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('100')
}, 1000)
})
p.then(
(value) => { console.log('成功了', value) },
(reason) => { console.log('失败了', reason) }
).catch(
(reason) => { console.log('失败了', reason) }
).finally(() => {
console.log('finally code')
})
// 成功了 100
// finally code
改变promise的状态
<!--
1. 如何改变一个Promise实例的状态?
(1)执行resolve(value): 如果当前是pending就会变为fulfilled
(2)执行reject(reason): 如果当前是pending就会变为rejected
(3)执行器函数(executor)抛出异常: 如果当前是pending就会变为rejected
-->
<script type="text/javascript" >
const p = new Promise((resolve,reject)=>{
console.log(a); //引擎抛异常
// throw 900 //编码抛异常
})
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
</script>
状态和回调顺序
<!--
改变Promise实例的状态和指定回调函数谁先谁后?
1.都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
2.如何先改状态再指定回调?
延迟一会再调用then()
3.Promise实例什么时候才能得到数据?
如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
-->
<script type="text/javascript" >
//先指定回调,后改变状态(最常见)
//#region
/* const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},4000)
})
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
) */
//#endregion
//先改状态,后指定回调
const p = new Promise((resolve,reject)=>{
resolve(100)
})
setTimeout(()=>{
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
},2000)
</script>
Then()的链式调用
<!--
Promise实例的值与状态的规则:
Promise实例.then()返回的是一个【新的Promise实例】,它的值和状态由什么决定?
1.简单表达: 由当前then()所指定的回调函数执行的结果决定(return 的结果)
2.详细表达:
(1)如果then所指定的回调返回的是非Promise值a:
那么【新Promise实例】状态为:成功(fulfilled), 成功的value为a
(2)如果then所指定的回调返回的是一个Promise实例p:
那么【新Promise实例】的状态、值,都与p一致
(3)如果then所指定的回调抛出异常:
那么【新Promise实例】状态为rejected, reason为抛出的那个异常
-->
<script>
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
})
p.then(
value => {console.log('成功了1',value); return Promise.reject('a')},
reason => {console.log('失败了1',reason);}
).then(
value => {console.log('成功了2',value);return true},
reason => {console.log('失败了2',reason); return 100}
).then(
value => {console.log('成功了3',value);throw 900},
reason => {console.log('失败了3',reason); return false}
).then(
value => {console.log('成功了4',value);return -100},
reason => {console.log('失败了4',reason);}
)
</script>
中断Promise链
<!--
中断promise链:
(1)当使用promise的then链式调用时, 在中间中断, 不再调用后面的回调函数。
(2)办法: 在失败的回调函数中返回一个pendding状态的Promise实例。return new Promise(()=>{})
-->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
function sendAjax(url,data){
return new Promise((resolve,reject)=>{
//实例xhr
const xhr = new XMLHttpRequest()
//绑定监听
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300) resolve(xhr.response);
else reject('请求出了点问题');
}
}
//整理参数
let str = ''
for (let key in data){
str += `${key}=${data[key]}&`
}
str = str.slice(0,-1)
xhr.open('GET',url+'?'+str)
xhr.responseType = 'json'
xhr.send()
})
}
//发送第1次请求
sendAjax('https://api.apiopen.top/getJoke',{page:1})
.then(
value => {
console.log('第1次请求成功了',value);
//发送第2次请求
return sendAjax('https://api.apiopen.top/getJoke2',{page:1})
},
reason => {console.log('第1次请求失败了',reason);return new Promise(()=>{})}
)
.then(
value => {
console.log('第2次请求成功了',value);
//发送第3次请求
return sendAjax('https://api.apiopen.top/getJoke',{page:1})
},
reason => {console.log('第2次请求失败了',reason);return new Promise(()=>{})}
)
.then(
value => {console.log('第3次请求成功了',value);},
reason => {console.log('第3次请求失败了',reason);}
)
</script>
错误穿透
<!--
promise错误穿透:
(1)当使用promise的then链式调用时, 可以在最后用catch指定一个失败的回调,
(2)前面任何操作出了错误, 都会传到最后失败的回调中处理了
备注:如果不存在then的链式调用,就不需要考虑then的错误穿透。
-->
<script>
//另一个例子演示错误的穿透
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(-100)
},1000)
})
p.then(
value => {console.log('成功了1',value);return 'b'},
reason => {throw reason}//底层帮我们补上的这个失败的回调
)
.then(
value => {console.log('成功了2',value);return Promise.reject(-108)},
reason => {throw reason}//底层帮我们补上的这个失败的回调
)
.catch(
reason => {throw reason}
)
</script>
promise的优势
优势:
1. 指定回调函数的方式更加灵活:
旧的: 必须在启动异步任务前指定
promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
2. 支持链式调用, 可以解决回调地狱问题
(1)什么是回调地狱:
回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件
(2)回调地狱的弊病:
代码不便于阅读、不便于异常的处理
(3)一个不是很优秀的解决方案:
then的链式调用
(4)终极解决方案:
async/await(底层实际上依然使用then的链式调用)
async和await
<!--
1. async修饰的函数
函数的返回值为promise对象
Promise实例的结果由async函数执行的返回值决定
2. await表达式
await右侧的表达式一般为Promise实例对象, 但也可以是其它的值
(1).如果表达式是Promise实例对象, await后的返回值是promise成功的值
(2).如果表达式是其它值, 直接将此值作为await的返回值
3. 注意:
await必须写在async函数中, 但async函数中可以没有await
如果await的Promise实例对象失败了, 就会抛出异常, 需要通过try...catch来捕获处理
-->
<script>
const p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
})
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('一些错误')
},2000)
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('c')
},4000)
})
;(async()=>{
try {
const result1 = await p1
console.log(result1);
const result2 = await p2
console.log(result2);
const result3 = await p3
console.log(result3);
} catch (error) {
console.log(error);
}
})()
await原理
<!--
若我们使用async配合await这种写法:
1.表面上不出现任何的回调函数
2.但实际上底层把我们写的代码进行了加工,把回调函数“还原”回来了。
3.最终运行的代码是依然有回调的,只是程序员没有看见。
-->
<script>
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},4000)
})
async function demo(){
//程序员“轻松”的写法
const result = await p
console.log(result);
console.log(100);
console.log(200);
//浏览器翻译后的代码
/* p.then(
result => {
console.log(result);
console.log(100);
console.log(200);
},
) */
}
demo()
console.log(1);
微任务和宏任务
/*
宏队列:[宏任务1,宏任务2.....]
微队列:[微任务1,微任务2.....]
规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务
1.如果有,先执行微任务
2.如果没有,按照宏队列里任务的顺序,依次执行
*/
//代码一
setTimeout(()=>{
console.log('timeout')
},0)
Promise.resolve(1).then(
value => console.log('成功1',value)
)
Promise.resolve(2).then(
value => console.log('成功2',value)
)
console.log('主线程')
//代码二
/* setTimeout(()=>{
console.log('timeout1')
})
setTimeout(()=>{
console.log('timeout2')
})
Promise.resolve(1).then(
value => console.log('成功1',value)
)
Promise.resolve(2).then(
value => console.log('失败2',value)
) */
//代码三
setTimeout(()=>{
console.log('timeout1')
Promise.resolve(5).then(
value => console.log('成功了5')
)
})
setTimeout(()=>{
console.log('timeout2')
})
Promise.resolve(3).then(
value => console.log('成功了3')
)
Promise.resolve(4).then(
value => console.log('失败了4')
)
</script>