前言
学习期约之前,我们先来简单的了解一下同步与异步
同步行为和异步行为是计算机科学的一个基本理念,可以理解为程序执行的两种模式
一、Promise是什么?
Promise是ES6新增的一个内置类(不兼容IE浏览器),我们叫他期约、或者约定。和人类语言相似,意思是对某个现阶段不存在的东西进行约定操作
Promise是一种设计模式(承诺者模式)
- 该设计模式的组成:立承诺 ==》根据承诺执行动作
- 要干什么事情、状态(成功,失败)、成功/失败后对应要做的事情。后续需要做的事情,按照之前的承诺去履行就行了。
Promise是为了管理“异步编程”代码,解决异步编程中回调的问题
二、同步和异步
1、同步行为
- 我们编写的每句代码,解析以后其实对应的就是一条条发送给系统的指令,在同步行为下,每条指令都会严格按照它们出现的顺序来执行,而
- 在同步行为下,程序执行的每一步都可以推断出程序的状态,这是因为后面的指令总是在前面的指令完成后才会执行。同步代码可以轻易的分析出程序在执行到任意代码位置时的状态。
下面举一个简单的例子:
fun()
console.log(2)
function fun() {console.log(1)}
console.log(3)
//控制台输出 1 2 3
由以上同步代码我们可以看出来,每条指令都会严格按照它们出现的顺序来执行
2、异步行为
- 异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须要等)。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待,所以我们的请求都会封装成异步操作。
-
相反的,我们可以将 “异步” 看成是 “不同步伐” 的执行代码,且异步代码是在不同的跑道,那么指令的执行顺序当然是不可预知的。
我们看一个简单的例子
let x = 10;
setTimeout(() => x = x + 10, 1000);
在上面这段代码中,第一排和第二排是同步代码,但是第二排里面的箭头函数是异步代码,也可以说是一个回调的过程,在1秒中以后才将该函数推入事件流执行的队列中去。
这涉及到了JavaScript的运行机制:单线程事件循环(Event Loop).
- 就是先处理主模块(主线程)上的同步任务, 再处理异步任务. 异步任务使用事件循环机制完成调度。通俗的来讲就是,JS代码会先循环执行,第一遍会先执行同步代码,如果解析到异步代码会将异步代码放到下一轮。等到同步代码都解析完毕才会解析异步代码
下面看一个例子来验证
let x = 10;
let y = 20;
setTimeout(() => {
x = x + 1
console.log(x)//11
});
console.log(x);//10
console.log(y)//20
//输出 10 20 11
如果按照正常思路来理解 上面代码中的setTimeout方法并没有设置延迟执行,是不是就应该立刻执行,然后执行里面的输出语句,然后再执行下面两个输出语句得到11,11, 20。
但是JavaScript 是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript 维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行,setTimeout()的第二个参数只是告诉 JavaScript 引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
期约
1、期约的定义、使用
实现步骤
- 设立一个承诺:也就是创建一个Promise实例
- 在Promise实例中,传递的参数必须是一个函数,否则将会报错(该函数可以是一个执行函数,一般是一段用于管理异步编程的代码)
- 在"new Promise"使,会将传递进去的函数立即执行
- 该函数中要有两个形参(resolve,reject),这两个参数的参数类型是新的函数
- 为了在需要的时候才去创建Promise,我们一般将创建Promise对象的代码封装在一个函数中
function newPro() {
return new Promise(() => {
setTimeout(() => {
console.log("Promise内置对象")
});
})
}
newPro()
2、then方法
- 如何保证匿名内部函数的代码运行完后再执行后面的代码?这时候就需要调用Promise里面 的then()方法(then就是然后的意思)
-
then()是为期约实例添加处理程序的主要方法
function newPro() { //首先通过方法封装一个期约对象
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Promise内置对象")
resolve();
}, 2000);
})
}
newPro().then(() => { //再通过对象调用then方法
console.log("这段代码会在resolve方法执行完后再执行")
})
console.log("这段代码以同步代码的形式执行")
在上段代码中我们可以看到then方法是接收了两个参数的 ,这两个参数实际上是针对期约的结果执行的处理程序 。分别是 onResolved处理程序和onRejected处理程序
- 这两个参数都是可选的,如果提供的话,则会在期约分别进入“”成功和失败“”状态时执行
- 当Promise期约返回的结果为resolve(成功)时,执行then的onResolve处理程序(即then的第一个参数)
- 当Promise期约返回的结果为reject(失败)时,执行then的onRejected处理程序(即then的第二个参数)
// 1、定义两个期约实例:
var pro1 = new Promise((resolve, reject) => {
resolve()
/给期约一个允许的状态
})
var pro2 = new Promise((resolve, reject) => {
reject()
//给期约一个拒绝的状态
})
// 2、定义不同期约状态的执行程序事件
function onResolved() {
console.log("期约允许")
}
function onRejected() {
console.log("期约拒绝")
}
// 3、调用then方法,根据期约的状态执行不同的处理事件
// pro1.then()方法输出期约允许
// pro2.then()方法输出期约拒绝
pro1.then(() => {
onResolved();//期约成功调用onResolved()方法
}, () => {
onRejected();
})
pro2.then(() => {
onResolved();
}, () => {
onRejected();//期约拒绝调用onRejected()方法
})
3、期约的三种状态、参数传递
- pending:待定
- resolve/fulfilled:兑换
- reject:拒绝
【注】:这里的兑现、拒绝并不是指程序上的成功或失败(报错),而只是程序逻辑上的成功或失败。(比如需要返回一个数值1,返回1就成功resolve,返回2则失败reject)
例如:
function newPro(num) {
return new Promise((resolve, reject) => {
if (num > 0) {
resolve("成功")
} else {
reject("失败")
}
})
}
var a = newPro(2)
console.log(a) //成功
var b = newPro(0)
console.log(b) //失败
期约状态与then的联用:
当期约实例中的状态函数被调用时,还会触发then方法的调用,并且还可以给then中的方法传递参数使用
function newPro() {
return new Promise((resolve, reject) => {
if (num > 5) { //如果随机数num大于5则期约成功
resolve(num)
} else {
reject(num)//否则失败
}
})
}
var num = Math.floor(Math.random() * 10);
newPro().then((num) => {
console.log(`数值大于5:${num}`) //当期约成功时,输出随机数的值
}, (num) => {
console.log(`数值不大于5:${num}`) //当期约失败时,输出随机数的值
})
4、链式操作
- 通过链式操作,我们可以连续的对期约求值,按顺序的进行异步操作。这里同样要用到then方法、
- 例如我们在购物软件购买商品时,一般需要经过以下三个步骤:选择商品、下单购买、收取快递。不管单独动作执行的时间是多少,但这一系列的动作一定是有先后顺序的。
//定义 “选择商品” 的期约实例
function tack1() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
// console.log("选择商品")
resolve("耐克");
}, 2000)
});
};
//定义“下单”的期约实例
function tack2() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
// console.log("下单购买")
resolve('付款999');
// reject("拒绝付款")
}, 2000)
});
};
//定义“收取快递”的期约实例
function tack3() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
// console.log("收快递")
resolve("张三");
}, 2000)
})
}
//通过链式操作,使异步代码有序的执行
tack1().then(function(data) {
console.log("你选择了商品")
console.log(data)
return tack2();
}).then(function(data) {
console.log("你支付了")
console.log(data)
return tack3();
}).then(function(data) {
console.log("快递信息为:")
console.log(data)
})
这里我们可以看到,我们使用链式操作将几个异步代码按我们需要的顺序执行
5、方法
catch()方法
- 与try....catch语法类似,Promise对象的catch方法也是用来捕获异常的。
- 这里的catch方法更多的是使用在链式操作上。我们知道,异步代码的报错是不会影响到第一轮同步代码的。但是,当我们通过链式语法操作多个异步代码是,那么前一个期约报错,后面的期约将会停止运行。但是我们可以使用catch方法来捕获异常
let newPro = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(9);
}, 2000)
})
newPro.then((num)=>{
console.log(`成功获取数值${num2}`)//这边找不到num2代码不会报错,会在
}).catch((num)=>{
console.log(`未找到num2`)
return newPro;
}).then((num)=>{
console.log(`成功获取数值${num}`)
return newPro;
})
如上面代码所示:在一节链式操作执行完以后,可以为该段链式进行异常捕获,在后面调用 catch() 方法即可。同样,catch方法也接受一个内置的匿名方法,作为报错后执行的操作。
Promise类提供两将多个期约实例组合成一个期约的静态方法:Promise.all()和Promise.race()。而期约合成后的行为取决于内部期约的具体行为
all()方法
- Promise.all()静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个可迭代的对象,返回一个新的期约
- 通过all创建的合成期约,必须等到所有的内部期约都执行成功后,才会执行。如果所有期约都成功解决,则合成期约的解决值就是所有内部期约解决值的数组,且按照迭代器顺序返回
- 如果期约拒绝,则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由(返回值),之后再拒绝的期约不会影响最终期约的拒绝理由。(也就是只有第一个出现拒绝的期约,才会将拒绝理由展示到外部,其余的拒绝理由不会展示),但是其他内部拒绝理由任然会执行,只不过不展示到外部
race()方法
- Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。这个方法接收一个可迭代对象,返回一个新期约
- 该方法返回的新期约,只会对第一个出现反应的内部期约有效果(无论状态)。同样的,奇特内部期约还是会执行,只不过不会影响到外部期约。
- 与all方法相反,race方法只要有一个内部版期约有结果,那么外部新期约就会执行