Promise的使用

promise是用来干什么的?

promise可以对一个异步任务进行封装,从而控制这个异步任务的执行次序并得到异步任务执行的结果。
比如,现在有这样一段代码:

setTimeout(() => {
    console.log(1);
}, 1000)
setTimeout(() => {
    console.log(2);
}, 1000)
setTimeout(() => {
    console.log(3);
}, 1000)
console.log(4)

这段代码运行结果相信大家都能预测到,控制台先输出4,一秒后输出1和2和3
那么现在有这样一个需求:我想在一秒后输出1,两秒后输出2,三秒后输出3,最后后输出4。
如果没有promise,那么我们只能这样写:

setTimeout(() => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
        setTimeout(() => {
            console.log(3);
            console.log(4)
        }, 1000)
    }, 1000)
}, 1000)

这种多层回调嵌套的写法又称回调地狱。既然都被叫做地狱了,那我们也不难知道这种写法不被推荐,因为随着逻辑的复杂和嵌套层数的增加,这种代码会显得非常混乱,不易阅读也不易维护。总而言之一个字,代码很丑。

但有了promise,我们能够以一种清晰且优雅的方式实现同样的功能,代码具有更好的封装性和可阅读性。
promise代码如下:

function printX(x)
{
    return new Promise((resolve, reject) =>{
        setTimeout(() => {
            console.log(x)
            resolve(x)
        }, 1000);
    })
}
printX(1).then(
    value => printX(2)
).then(
    value => printX(3)
).then(
    value => {console.log(4)}
)

promise的基本使用

Promise构造函数

Promise(executor){…}

executor是执行器函数:

executor: function(resolve, reject){…}

在生成promise对象时,往往采用这样的写法:

let p = new Promise( (resolve, reject) =>{/ *函数内容 */} )
在对象生成的时候会立即执行executor

executor:(resolve, reject) =>{/ *函数内容 */} 是实参,resolve和reject是形参,可以随意命名,但不建议随意命名。

执行器函数里的resolve和reject

executor里的resolve和reject是两个函数,函数的具体内容不需要我们操心,它们是写在Promise构造函数当中的,我们只需要知道这两个函数有什么功能就行了。

promise对象有三种状态,分别为pending resolved rejected。

promise对象的初始状态为pending,当调用resolve()函数时状态会做出pending->resolved(也叫做fullfilled)的转变,同时将resolve()的参数设置为该对象的结果值。reject()与之类似,会使状态转变为rejected,同时将reject()的参数设置为该对象的结果值

除了resolve和reject,throw语句也能改变promise的状态,如throw(reason)会使状态变为rejected,同时把reason设置为结果值。

一般在运行成功时执行resolve函数,把成功得到的结果作为参数,在运行异常时执行reject函数,把异常的原因作为参数。
非要反过来也可以,resolve代表异常、reject代表成功当然也行得通,但不建议。

promise对象的状态仅能够改变一次,promise的结果值也仅能够改变一次

Promise.prototype.then()

let p = new Promise( (resolve, reject) =>{/ *函数内容 */} )
p.then( value => {/ *函数内容 */}, reason => {/ *函数内容 */} )

当p的状态改变时,会自动调用then()方法。
如果p的状态为resolved,会调用then()方法参数中的第一个函数,该函数的参数value是p中的结果值。
如果p的状态为rejected,会调用then()方法参数中的第二个函数,该函数的参数reason是p中的结果值。
value和reason都是形参,可以随意命名,但不建议。
then()方法返回的仍然是一个promise对象,所以支持链式调用

p.then(…).then(…).then(…).then(…).then(…).then(…).then(…)…

一个promise对象可以指定多个then()方法,不管对象的状态是resolved还是rejected,每个then()方法都会被执行

p.then(…1)
p.then(…2)
p.then(…3)

既然then返回的是一个promise对象,那么这个对象的状态和结果值是由什么决定的呢?
答案:由then中以value和reason为形参的函数返回值决定。
如果调用的是value那个函数,那么就由value那个函数的返回值决定,反之亦然。
姑且把这个返回值叫做val,把then()返回的promise对象称为pro
如果val是一个promise对象,那么pro的结果值为val的结果值,pro的状态为val的状态。
如果val不是promise对象,那么pro的结果值为val,pro的状态为resolved

如何中断链式调用的promise呢?
很简单,让某个then返回的promise对象为pending即可,因为状态为pending的promise对象是不会调用then方法中的回调。

p.then(
    value => new Promise(() => {}),
    reason => new Promise(() => {})
).then(...).then(...).then(...).then(...).then(...) ...
	//后面的then方法都不会被执行

promise的值穿透与catch

在promise的链式调用中,如果发现当前then()方法中没有传入能够处理当前promise的回调函数(没有传入参数或者传入的参数类型不是Function),那么会直接跳过这个then,就当它不存在似的,开始调用下一个then。就像隔空打老牛似的,promise的结果值可以掠过那些不能处理它的then()方法,直接跳到能处理它的then()方法那里去,有一种穿透效果,所以称之为值穿透。
描述的有些抽象,但代码很形象:

let p = Promise.resolve(123)
p.then().then().then().then().then().then(value => {console.log(value)}) //输出 123

123这个值,完美地穿透了五个then(),到达了最后那个回调。

值穿透特性可以用在异常捕捉上面,也就是说我们可以只在链式调用的末尾加上一段处理reason的回调函数,而在前面不需要再写处理reason的回调函数,这样就大大简化了代码。catch()和then()一样也是promise原型对象中的方法,不同的是catch()的参数只有一个处理异常的回调函数。

p.then(value => {…}, reason => {…})
p.catch(reason => {…})

样例:

let p = Promise.reject(123)
p.then( value => {} ).then(value => {}).then(value => {}).catch(reason => {console.log(reason)})
//控制台输出123

Promise API

Promise.resolve(),Promise.reject()

Promise.resolve(value)

如果value是promise对象,那么返回这个promise对象。

let p1 = Promise.reject(123)
let p2 = Promise.resolve(123)
console.log(p1 === Promise.resolve(p1), p2 === Promise.resolve(p2));
//true true 

如果不是,那么返回一个状态为resolved,结果值为value的promise对象。

Promise.reject(value)

和上面不太一样,不管value是什么,Promise.reject(value)都直接返回一个状态为rejected,结果值为value的promise对象。
验证:

let p1 = Promise.reject(123)
let p2 = Promise.resolve(123)
Promise.reject(p1).then(value => {}, reason => {console.log(reason === p1);})
Promise.reject(p2).then(value => {}, reason => {console.log(reason === p2);})
Promise.reject(123).then(value => {}, reason => {console.log(reason);})
//true true 123

Promise.race(), Promise.all()

Promise.race(promiseArray)

参数:promiseArray是一个promise数组(也可以是Map或者Set)
返回值:race()返回一个promise对象,该对象初始状态为pending,只要参数中任何一个promise元素的状态变为rejected或resolved,该对象的状态和结果值会变成参数中第一个变为rejected或resolved的元素的值。

Promise.all(promiseArray)

参数:promiseArray是一个promise数组(也可以是Map或者Set)
返回值:all()返回一个promise对象,该对象初始状态为pending,只有当参数中所有的promise元素均为resolved,该对象才会变成resolved,只有当参数中任意一个promise元素变为rejected,该对象就会变成rejected。
如果参数中所有的promise最终都是resolved,那么该对象的结果值是一个数组,数组元素包括所有参数值promise对象的结果值;否则,该对象的结果值是参数中第一个变成rejected的promise对象的结果值。
当promise对象比较少时,我们可以逐个使用promise.then来得到结果值。当promise对象数量较多时,使用Promise.all()可以方便地一次性得到所有的

async和await

对于这样一个async函数:

async function fn(){
    /*
        函数内容...
    */
    return value //value可以代表一个变量名,也可以代表一个表达式
}
let result = fn()

如果value是promise对象,那么result就是一个promise对象,它的结果值和状态都等于value的。
如果value不是promise对象,那么result还是一个promise对象,它的结果值等于value,状态为resolved。
如果没有函数内没有写return语句,那么等同于return undefined。

await只能在async函数中使用:
使用方式为:

function f1(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(111)
            resolve(222)
        }, 2000)
    })
}
async function fn(){
    let result1 = await f1()
    let result2 = await (() => 333)() //等同于await 333 等同于await Promise.resolve(333)
    console.log(result1)    //输出 222
    console.log(result2)    //输出 333
}
fn()
console.log(444) 
//输出结果:先输出444,两秒后先后输出111 222 333

当await后面跟的是promise对象时,await的值等于promise的结果值,并且async函数就像是被按了暂停键,在得到结果之前函数内后续的代码均不会执行。也就是说,await实现了异步任务的同步执行,前面promise的链式调用也能达到同等效果,但相比之下await用起来可就简单得多了。

当await后面跟的不是promise对象时,会被自动包装成一个状态为resolved的promise对象,比如await 123会被包装成await Promise.resolve(123)

使用回调函数实现Promise

使用回调函数来实现Promise,对Promise原理和源码的更深理解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值