什么是Promise?
它是一个ES6提出的一个新语法,用来优化异步代码的写法。 在前端领域是我们必须要会的内容 它可以解决前端代码中的回调地狱问题。也是面试中面试官常问的点。
一张图了解回调地狱:
Promise的基本使用
//异步操作 resolve(obj1) 或者 reject(obj2)
});
p1.then(function(rs){
// 如果p1的状态是resolved,则then中的函数
//会执行,且obj1的值会传给rs
}).catch(function(rs){
// 如果p1的状态是reject,则catch中的函数
// 会执行,且obj2的值会传给rs
}).finally(function(){
// 一定会执行的函数
})
构造器
// const obj = new Object()
// const arr = new Array()
const p1 = new Promise(function(resolve,reject){
// 执行异步代码
// 调用resolve,或者reject
});
console.dir(p1)
- 构造器必须要给定一个参数,如果不给就是会报错。例如,
new Promise()报错的信息是:Promise resolver undefined is not a function - 构造器的实参是一个函数,这个函数的特殊之处在于它有两个形参(resolve,reject),这两个形参也是函数。在格式上,也可以采用箭头函数来改写。例如:
var p1 = new Promise((resolve,reject)=>{})。 - 在函数体的内部, 一般会执行异步代码,然后根据情况来调用resolve()或者是reject() 。调用resolve或者是reject后会产生什么样的后果,在后面小节介绍。 当然了,再次强调一下resolve和reject只是形参名,可以改写成其它的。
promise的三种状态和值
一个promise对象的状态可能是以下三种之一:pending,resolved,rejected。
初始值pending pending。它的意思是‘待定的,将要发生的,相当于是一个初始状态,创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态,这个初始状态会随着你调用的resolve,或则是reject函数而切换到另一种状态。
成功状态resolved(也有觉fulfilled) resolved。表示解决了,就是说这个承诺实现了。要实现从pending到resolved的转变,需要创建Promise对象时,在函数体中调用了resolved方法(即第一个参数)。
resolve和reject仅是形参名而已。
失败状态rejected rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。
三种状态总结:
1.状态是可转化的 最初船舰promise对象时,默认状态是pending,如果在函数体内部调用了第一个参数对应的函数,则状态变成了resolved,如果调用了第二个参数对应的函数,则状态变成了rejected
pending ----- resolve() --> resolved;
pending ----- reject() --> rejected;
2.状态转换是不可逆的
一旦从pending————>resolved(或则是rejected),就不可能再回到pending,也不能由resolved变成rejected
promise的值:
一个promise对象处理状态之外,还有promiseValue。在构造器中,这个值在调用resolve和reject方法时传入。
var p = new Promise( (resolve,reject) => { resolve(123); } );
// 此时,prommise对象p的状态是 resolved,值是123。
console.dir(p)
var p = new Promise( (resolve,reject) => { resolve(123); } );
// 此时,prommise对象p的状态是 resolved,值是123。
console.dir(p)
promise的兑现:
在构造器的函数体中,一旦状态发生了变化,就会then,或则是catch中去,同时把promiseValue传入对应的函数。
具体来说:
- 状态从pending变成resloved,进入then中,调用函数,并传入此时的promiseValue(就是调用reslove时传入的实参)
- 状态从pending变成rejected,进catch中,调用函数,并传入此时的promiseValue(就是调用reject时传入的实参)
then的返回值(难点)
then(方法的返回值也是一个promise对象,所以它支持链式写法。但是要注意的是它的返回值是一个promise对象,与调用的then方法的并不是同一个对象。
请参考如下代码:
var p1 = new Promise(()=>{});
var p2 = p1.then(function f_ok(){}, function f_err(){});
// p2也是一个promise对象。
console.log(p1 === p2); // false
如上代码可以说明p1.then()的结果是一个与p1不同的promise对象。换句话说,then()会封装一个全新的promise对象p2。那既然 p2也是一个promise对象,那么,p2的状态(promiseStatus)和值(promiseValue)分别是什么?
p2的状态及promiseValue如何确定?(理解此处 ,重要点)
p2的状态及promiseValue按如下规则来确定
- 如果p1的状态是pending,则p2的状态也是pending。
- 如果p1的状态是resolved,then()会去执行f_ok,则p2的状态由f_ok的返回值决定。
-
- 如果f_ok返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_ok函数的return值。
- 如果f_ok返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
- 如果f_ok这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
- 如果p1的状态是rejected,then()会去执行f_err,则p2的状态由f_err的返回值决定。
-
-
如果f_err返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_err函数的return值。
-
如果f_err返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
-
如果f_err这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
-
以下练习题请参考:
练习一:
var p1 = new Promise(()=>{});
var p2 = p1.then(function f_ok(){}, function f_err(){}); // p2也是一个promise对象。
console.dir(p1); // pending
console.dir(p2); // pending
练习二:
var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
return 1
}, function f_err(){}); // p2也是一个promise对象。
console.dir(p1); // resolved, undefined
console.dir(p2); // resolved, 1
练习三:
var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
var temp = new Promise((resolve,reject)=>{ resolve({a:1}) });
return temp;
}, function f_err(){});
console.dir(p2); // resolved, {a:1}
练习四:
var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
console.log(abc);// 这里故意犯错
}, function f_err(){});
console.dir(p2); // rejected VM112:3 Uncaught (in promise) ReferenceError: abc is not defined
练习五:
var p1 = new Promise((resolve,reject)=>{ reject(100)});
var p2 = p1.then(function f_ok(){
}, function f_err(errVal){
var temp = new Promise((resolve,reject)=>{ resolve({b:errVal}) });
return temp;
});
console.dir(p2); // resolved {b:100}
练习六:
var p1 = new Promise((resolve,reject)=>{ reject(100)});
var p2 = p1.then(function f_ok(){
}, function f_err(errVal){
throw new Error("aaa")
});
console.dir(p2) // rejected Uncaught (in promise) Error: aaa
at f_err (<anonymous>:5:8)
catch的格式及用法
catch 是 then(null, reject)的别名
catch()的格式及用法
Promise.prototype.catch 是 Promise.prototype.then(null, reject)的别名,用于指定当promise对象的状态从pending变成rejected的回调函数 。
catch的返回值
catch的返回值仍是一个promise对象,确定它的值的方式与then(null,(errVal)=>{ })的方式一致。
promise的链式调用
链式调用: $().attr().val()
p.then().then().then()...
示例代码:
function do1() {
console.log("任务1");
}
function do2() {
console.log("任务2");
}
function do3() {
console.log("任务3");
}
function do4() {
console.log("任务4");
}
var p = new Promise((resolve,reject)=>{ resolve()})
p.then(do1)
.then(do2)
.then(do3)
.then(do4);
结果输出是:任务1,任务2,任务3,任务4
var p1 = p.then(do1);
var p2 = p1.then(do2)
var p3 = p2.then(do3)
var p4 = p3.then(do4)
第一步:由于p的状态是resolved,所以p.then(do1)中,do1函数会执行。输出任务1。
第二步:确定p1的状态。按前面关于then的部分的介绍,p1的状态由do1()来决定。因为do1并没有明确指定返回值,则返回值就是undefined. p1的状态就是resolved。
第三步:由于p1的状态是resolved,所以p1.then(do2)会继续执行do2。输出任务2 ,且p2的状态由do2来决定。与第二步的分析相同,p2的状态仍是resolved。
第四步:接下来看p3。由于p2的状态是resolved,所以p2.then(do3)会继续执行do3。输出任务2 ,且p3的状态由do3来定,仍是resolved。
最后:p3.then(do4)。由于p3的状态是resolved,所以执行do4。输出任务4 。
示例代码2:
function do1() {
console.log("任务1");
}
function do2() {
console.log("任务2");
}
function do3() {
console.log("任务3");
}
function do4() {
console.log("任务4");
}
var p = new Promise((resolve,reject)=>{ resolve()})
p.then(do1)
.then(do2)
.catch(do3)
.then(do4);
上面的代码的执行结果是:任务1, 任务2, 任务4。我们先把过程分解一下,多添加几个中间变量来描述它们的值:
分析如下:
第一步:由于p的状态是resolved,所以p.then(do1)中,do1函数会执行。输出任务1。
第二步:确定p1的状态。按前面关于then的部分的介绍,p1的状态由do1()来决定。因为do1并没有明确指定返回值,则返回值就是undefined. p1的状态就是resolved。
第三步:由于p1的状态是resolved,所以p1.then(do2)会继续执行do2。输出任务2 ,且p2的状态由do2来决定。与第二步的分析相同,p2的状态仍是resolved。
第四步:接下来看p3。由于p2的状态是resolved,所以它并不会执行do3, p3的状态没有变化,仍保持p2的状态:resolved。
最后:p3.then(do4)。由于p3的状态是resolved,所以执行do4。输出任务4 。
示例代码3:
function do1() {
console.log("任务1");
console.log(abc); //故意犯错
}
function do2() {
console.log("任务2");
}
function do3() {
console.log("任务3");
}
function do4() {
console.log("任务4");
}
var p = new Promise((resolve,reject)=>{ resolve()})
p.then(do1)
.then(do2)
.then(do3)
.catch(do4);
上面的代码的执行结果是:任务1, 任务4。我们先把过程分解一下,多添加几个中间变量来描述它们的值:
分析如下:
第一步:由于p的状态是resolved,所以p.then(do1)中,do1函数会执行,输出任务1,同时由于这里有段错误代码,所以p1的状态就是rejected。
第二步:由于p1的状态是rejected,所以p1.then(do2) 就不会执行do2,同时p2的状态也是rejected。
第三步:对p3的分析也是如此。
第四步:p3的状态是rejected,所以会执行do4。
示例代码4
function increment(value) {
return value + 1;
}
function doubleUp(value) {
return value * 2;
}
function output(value) {
console.log(value);// => (1 + 1) * 2
}
var p = Promise.resolve(1);
p.then(increment)
.then(doubleUp)
.then(output)
上面代码的输出结果是4。
回调地狱的问题
示例代码
简单的后台接口示例:
// 静态资源托管,直接访问public/api.html
const express = require('express')
const app = express()
var cors = require('cors')
// 处理跨域请求
app.use(cors())
app.use( express.static('public') )
// 接口1 get
app.get('/get', (req, res) => {
res.send([{id:1, name:'体育'}])
})
app.get('/getCategory', (req, res) => {
if(req.query.id==1){
res.send([{id:12, name:'NBA新闻'}, {id:13, name:'足球新闻'}])
}else {
res.send([{id:10, name:'羽毛球'}])
}
})
app.get('/getNews', (req, res) => {
if(req.query.id==12){
res.send([{id:1, name:'谁能得到总冠军'}, {id:2, name:'湖人已经不行了么?'}])
}else {
res.send([])
}
})
app.listen(3000)
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
function getData({url,success}) {
const xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = function (){
if(this.readyState === 4){
// 加载完成
const d = JSON.parse(xhr.responseText);
success(d)
}
}
xhr.send()
}
function getDataPromise (url) {
return new Promise((resolve,reject) => {
const xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = function (){
if(this.readyState === 4){
// 加载完成
const d = JSON.parse(xhr.responseText);
resolve(d)
}
}
xhr.send()
})
}
getData({
url:'http://localhost:3000/get',
success: (data)=>{
getData({
url:'http://localhost:3000/getCategory?id=1',
success: data => {
console.log(data)
getData({
url:'http://localhost:3000/getNews?id='+data[0].id,
success: data => {
console.log(data)
}
})
}
})
}})
getDataPromise('http://localhost:3000/get').then(data=>{
return getDataPromise('http://localhost:3000/getCategory?id='+data[0].id)
}).then(data => {
return getDataPromise('http://localhost:3000/getNews?id='+data[0].id)
}).then(data => {
console.log(data)
})
async function get() {
const res1 = await getDataPromise('http://localhost:3000/get')
const res2 = await getDataPromise('http://localhost:3000/getCategory?id='+res1[0].id)
const res3 = await getDataPromise('http://localhost:3000/getNews?id='+res2[0].id)
console.log(res3)
}
get()
</script>
</body>
</html>
回调地狱-用promise来优化
const f = (filepath) => {
const p = new Promise((resolve, reject)=>{
// 这里写具体的代码,并在某个恰当的时机去调用resolve和reject函数。
fs.readFile(filepath,'utf-8',(err, data)=>{
if(err) {
// console.log(err);
reject(err)
} else {
resolve(data)
// console.log(data)
}
})
})
return p
}
let str = ""
f('./1.txt').then(str1 => {
console.log(str1)
str+=str1
return f('./2.txt')
}).then((str2)=>{
str+=str2
console.log(str2)
return f('./3.txt')
}).then(str3=>{
str+=str3
console.log(str3)
console.log(str)
})
async-await语法
async,await 是es7中新增的语法,用来进一步改进异步代码的写法,是promise升级版!
async函数返回一个 Promise 对象。
async函数内部return语句返回的值是Promise 对象的值
await 命令
await的外层函数必须有一个async.
正常情况下,await命令后面是一个 Promise 对象,返回该promise的值。如果不是 Promise 对象,就直接返回对应的值。
示例:
<script>
async function f() {
const p = new Promise((resolve,reject)=>{
resolve(100)
})
const a = 1
// await 等待promise的状态变化完成(pending->resolved, pending->rejected)
// 取出promise的值
const b = await p
console.log(a,b)
}
f()
</script>
用async和await优化回调地狱问题
async function fnAxios() {
const res = await axios.get('http://localhost:3000/get')
console.log('第1次请求', res)
const res1 = await axios.get('http://localhost:3000/getCategory?id=' + res.data[0].id)
console.log('第2次请求', res1)
const res2 = await axios.get('http://localhost:3000/getNews?id=' + res1.data[0].id)
console.log('第3次请求', res2)
}
fnAxios()