目录
它是一个ES6提出一个新语法,用来优化异步代码的写法。
-
在前端领域是我们必须要会的一个内容
-
后续的课程中也会多次使用
promise:承诺。
-
生活中.它是用来表述
对将来要发生的事情的肯定
。 例如 : 高中生说,老师,我会考上一所好大学的;销售员说,老板,我一定会签下大单的;程序员说,老妈,我过年一定会带个女朋友回来的。 -
在ES6中,它是新增的构造器(Array, Object, Promise),用来优化实现异步操作。在没有它之前,javascript中的异步处理,大多是利用回调函数来实现的。典型的几种如下:(1)setTimeout (2)ajax (3)nodejs中的文件读取。现在有了promise,就可以对这些异步操作进行改写了。
学习内容
-
构造器
-
三种状态和值
-
经典调用格式
-
then的执行逻辑和返回值
-
then链式用法($().x().x())
-
async await
小小一道面试题
代码填空,并完成指定功能。
function sleep(time){
// 请写出你的代码
}
sleep(2000).then(()=>{
console.log("后续操作")
})
console.log(2);
目标 是让sleep 的功能与setTimeout一样:就是等2000毫秒之后再执行后续操作。
参考代码如下:
function sleep(time){
return new Promise(function(resolve,reject){
// 异步操作,根据执行结果,决定是否调用 resolve,reject
setTimeout(function(){
resolve()
}, time)
})
}
Promise的基本使用
经典格式
构造器
// 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对象的状态可能是如下三种之一:pending
,resolved
,rejected
。下面分别介绍。
初始态pending
pending。它的意思是 "待定的,将发生的",相当于是一个初始状态。 创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
var p = new Promise((ok,err)=>{ console.info('发呆.....' )})
console.dir(p)
这个初始状态就是你对别人说出承诺的那个瞬间,此时,大家都不知道这个承诺是否能兑现。
成功态resolved
也有叫fulfilled的。
resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法(即第一个参数)。
var p = new Promise((resolve,reject)=>{ resolved();})
console.dir(p)
注意,上面的resolve和reject仅是形参名而已。
失败态rejected
rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。
var p = new Promise((resolve,reject)=>{ reject()} )
console.dir(p)
三种状态小结
-
状态是可转化。
最初创建promise对象时,默认状态是pending,如果在函数体内部调用了第一个参数对应的函数,则状态变成了resolved;如果调用了第二个参数对应的函数,则状态变成了rejected。
pending ----- resolve() --> resolved; pending ----- reject() --> rejected;
-
状态转换是不可逆的。
一旦从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) => { reject(123); } );
// 此时,prommise对象p的状态是 rejected,值是123
console.dir(p)
promise的兑现
在构造器的函数体中,一旦状态发生了变化,就会进行then,或者是catch中去,同时把promiseValue传入对应的函数。
具体来说:
-
状态从pending变成resolved,进入then中,调用函数,并传入此时的promiseValue(就是调用resolve时传入的实参)
-
状态从pending变成rejected,进入catch中,调用函数,并传入此时的promiseValue(就是调用reject时传入的实参)
根据实际情况的需要,也可以不加上finally()。
小结
构造器
三种状态和值
经典格式
使用promise改造回调函数
下面我们回到具体的问题:如何对现有的,通过回调函数来实现异步操作的代码进行改写。
基本套路
-
第一步:建立模板。这里的模板是指固定的套路:写一个空函数,在函数体中的创建一个promise对象,并返回。
function fnName(){ var p = new Promise((resolve,reject)=>{}) return p; }
当然, 如果使用箭头函数的话,还可以简化成这样:
let fnName = () => new Promise((resolve,reject)=>{})
-
第二步:把异步功能写入构造器中,根据实际来决定是否调用resolve,reject。
function fnName(){ var p = new Promise((resolve,reject)=>{ // 这里写具体的代码,并在某个恰当的时机去调用resolve和reject函数。 }) return p; }
-
第三步:调用函数。
通过
fnName().then().catch()
结构来调用这个函数。
示例1: ajax
原回调函数的写法
var xhr = new XMLHttpRequest();
xhr.open('get','http://httpbin.org/ip')
xhr.onload = function() {
console.log(xhr.responseText)
}
xhr.onerror = function (){
console.log('请求接口错误')
}
xhr.send()
改写之后
let ajax = function() {
return new Promise((resolve,reject)=>{
var xhr = new XMLHttpRequest();
xhr.open('get','http://httpbin.org/ip')
xhr.onload = function() {
resolve(xhr.responseText)
}
xhr.onerror = function (){
reject('请求接口错误')
}
xhr.send()
})
}
ajax().then(res =>{
console.log(res)
}).catch(err =>{
console.log(err)
})
这里用到的接口http://httpbin.org/ip
是从http.org 上访问的。
示例2 nodejs中的文件读取
原来的写法
const fs = require('fs');
fs.readFile("1.txt", 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
改写之后
const fs = require('fs');
function readFile(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
readFile('./server.js1')
.then(rs => {
console.log(rs);
})
.catch(err => {
console.log('err', err);
});
then的格式及执行逻辑
目标
掌握then方法的使用;
作用
then方法的作用是为Promise对象添加状态改变时的回调函数。
下面从其调用格式,执行逻辑及返回值三个方面来介绍
then的格式
它可以写两个参数,如下:
// p 是一个promise对象 p.then(函数1[,函数2])
它的两个参数都是函数。
-
第一个参数是resolved状态的回调函数。当p的状态从pending变成了resolved之后,函数1会执行。
-
第二个参数是rejected状态的回调函数。 当p的状态从pending变成了rejected之后,函数2会执行。
-
其中第二个参数是可选的,如果只写一个参数的话就是如下:
promise对象.then(函数1)
执行逻辑
以如下代码为例
var p = new Promise((resolve,reject)=>{
resolve(val1);
// or reject(val2)
})
p.then((okVal)=>{
console.info("成功");
console.log(okValue);
}, (errVal)=>{
console.info("失败");
console.log(errValue);
})
它的两个参数都是函数,其执行逻辑是:
-
如果promise对象的状态是resolved,则then()会执行第一个函数,并传入当前的PromiseValue(即上面的val1);
-
如果promise对象的状态是rejected,则then()会执行第二个函数,并传入当前的PromiseValue(即上面的val2);
-
特别地,如果promise对象的状态是rejected,且此时then方法并没有设置第二个参数,就会向外抛出一个错误,错误的提示大约是
Uncaught (in promise)
。
示例代码1
var p = new Promise((resolve,reject)=>{
resolve(1)//主动调用resolve,并传入
})
// 此时,P的状态是resolved,且值promiseValue 是1.
p.then((res)=>{
// 因为p的状态是resolved,所以自动执行then的第一个参数,并且把promisevalue传进来。
console.log("then,ok",res)
})
执行输出结果是:then,ok,1
示例代码2
var p = new Promise((resolve,reject)=>{
reject(2)//主动调用reject,并传入实参
})
// 此时,P的状态是rejected,且值promiseValue 是2.
p.then((res)=>{
// 因为p的状态是resolved,所以这句代码不会执行。
console.log("then,ok",res)
},(err)=>{
// 因为p的状态是rejected,所以自动执行then的第二个参数,并且把promisevalue传进来。
console.log("then,err",err)
})
执行输出结果是:then,err,2
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按如下规则来确定
-
如果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就是这个错误对象。
-
练习
示例代码1
var p1 = new Promise(()=>{});
var p2 = p1.then(function f_ok(){}, function f_err(){}); // p2也是一个promise对象。
console.dir(p1);
console.dir(p2);
示例代码2
var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
return 1
}, function f_err(){}); // p2也是一个promise对象。
console.dir(p1);
console.dir(p2);
特殊地,如果f_ok()中并没有return语句,则相当于是 return undefined。
示例代码3
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);
示例代码4
var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
console.log(abc);// 这里故意犯错
}, function f_err(){});
console.dir(p2);
示例代码5
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);
示例代码6
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)
catch的格式及用法
catch()的格式及用法
Promise.prototype.catch 是 Promise.prototype.then(null, reject)的别名,用于指定当promise对象的状态从pending变成rejected的回调函数 。
var p1 = new Promise((resolve,reject)=>{
reject('s')
});
p1.catch(function(err){
console.log(err);
})
// 与下面的代码等价
p1.then(null, function(err){
console.log(err);
})
单独使用catch没有什么意义,它一般与then一块使用。如下:
new Promise((resolve,reject)=>{
}).then(function(result){
// 如果promise对象的状态是resolved的,就会到这里来,并且result会接收promiseValue的值
}).catch(function(err){
// 如果promise对象的状态是rejected的,就会到这里来,并且err会接收promiseValue的值
})
// 上面的代码如何拆分来写的话,等价于:
var p1 = new Promise((resolve,reject){
});
var p2 = p1.then(function(result){
});
var p3 = p2.catch(function(err){
})
catch的返回值
catch的返回值仍是一个promise对象,确定它的值的方式与then(null,(errVal)=>{ })
的方式一致。
promise的链式调用
示例1
代码
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
。我们先把过程分解一下,多添加几个中间变量来描述它们的值:
var p1 = p.then(do1);
var p2 = p1.then(do2)
var p3 = p2.catch(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,所以它并不会执行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
。我们先把过程分解一下,多添加几个中间变量来描述它们的值:
var p1 = p.then(do1)
var p2 = p1.then(do2)
var p3 = p2.then(do3)
var p4 = p3.catch(do4)
分析如下:
第一步:由于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。
分析
拆分 之后,分析如下:
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)
async语法
async,await 是es7中新增的语法,用来进一步改进异步代码的写法,是promise升级版!
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。
async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值是Promise 对象的值
function f1 () {
return 1
}
async function f2 () {
return 1
}
async function f3 () {}
const r1 = f1()
const r2 = f2()
const r3 = f3()
console.log(r1) // 1
console.log(r2) // Promise, resolved(1)
console.log(r3) // Promise, resolved(undefined)
r2.then(res => { console.log(res) })
r3.then(res => { console.log(res) })
await 命令
正常情况下,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函数(设名为asyncF)时,进入函数内部:
- 按序执行同步代码 - 遇到await,跳出asyncF函数,
继续执行后续代码。
当await的后的异步代码执行完成之后,接着执行asyncF中的后续代码。
const p1 = new Promise((resolve, reject) => {
setTimeout(()=>resolve(100), 3000)
})
async function f () {
console.log('开始执行f')
var a = await p1
console.log('p1完成了')
console.log(a)
return 1
}
console.log('主函数运行...')
const res = f()
console.log('f()完成,返回值是', res)
res.then(res => { console.log(res) })
执行结果是:
主函数运行... 开始执行f f()完成,返回值是 Promise { <pending> } ----此处等了3s---- p1完成了 100 1
async的返回值
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve(100), 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(200), 5000)
})
async function f () {
console.log('开始执行f')
var a = await p1
console.log('p1完成了')
var b = await p2
console.log('p2完成了')
return a + b
}
console.log('主函数运行...')
const res = f()
console.log('f()完成,返回值是', res)
res.then(res => { console.log(res) })
输出结果
主函数运行...
开始执行f
f()完成,返回值是 Promise { <pending> }
----此处等了3s----
p1完成了
----此处等了2s----
p2完成了
300