前端战五渣学JavaScript——Promise

我是要成为海贼王的男人

悟空已成神,鸣人已成影,待路飞成王之时,便是我青春结束时!

悟空陪布玛找寻龙珠,一路拳打比克、斩弗利萨,生个儿子战沙鲁,最后净化布欧,只因承诺要保护地球。鸣人“有话直说,说到做到,这就是我的忍道”,一句会把佐助带回来的承诺,断臂践行。路飞要凑齐10个船员,成为海贼王,我们相信路飞一定会成王,因为我们相信他的承诺。

我为什么说承诺呢,今天主题不是Promise吗,因为⬇️

回调地狱 Callback Hell

如果看这篇文章的你是有过项目经验的,应该都遭遇过这惨绝人寰的“回调地狱”。“回调地狱”并不是JS或者编程语言中的一种形式,只是大家把这种编程中遇到的现象、问题预定俗称的调侃成“回调地狱”。因为只要陷进去,就很难出来。并且回调地狱在代码层级上会越陷越深,逻辑看着会非常会乱,如下代码⬇️

// 我们用setTimeout模拟线上发送请求等异步执行的函数
setTimeout(() => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
        setTimeout(() => {
            console.log(3);
        }, 1000)
    }, 1000) 
}, 1000);
复制代码

这是三个回调函数嵌套,延迟一秒后输出1,再过一秒输出2,再过一秒输出3。当然现实项目中,每个函数里面处理的逻辑肯定不仅仅只是输入一个数字这么简单,当我们回调嵌套很多的时候,如果产品提出的一个需求我们需要更改执行顺序,这个时候我们会发现嵌套逻辑复杂到难以简单的更改顺序,严重的只能重新写这段的逻辑代码。并且回调函数让逻辑很不清晰。
后来就有人提出了Promise概念,这个概念意在让异步代码变得非常干净和直观。

Promise 这就是我的忍道

这个概念并不是ES2015首创的,在ES2015标准发布之前,早已有Promise/APromise/A+等概念的出现,ES2015中的Promise标准便源自于Promise/A+Promise最大的目的在于可以让异步函数变得竟然有序,就如我们需要在浏览器中访问一个JSON座位返回格式的第三方API,在数据下载完成后进行JSON解码,通过Promise来包装异步流程可以使代码变得非常干净。———————摘自《实战ES2015》

上面最重要的一句就是可以让异步函数变得竟然有序,可能有人会说awaitasync也可以让异步函数同步执行,但是await操作符本来就是用于等待一个Promise对象的。
我们先来看一下Promise是怎么解决上面回调地狱这样的难题的⬇️

// 封装一层函数
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
  })
}
// 按回调函数的逻辑执行
timeout().then(() => {
    console.log(1);
    return timeout()
}).then(() => {
    console.log(2);
    return timeout()
}).then(() => {
    console.log(3);
});
复制代码

我们按照回调函数的逻辑用Promise重新写了一遍,执行结果一样,我们可以看出来,相比回调函数的层级深入,使用Promise以后函数的层级明显减少了,逻辑清晰许多。


下面我们来从头开始认识Promise

Promise基础

想要给一个函数赋予Promise的能力,就要先创建一个Promise对象,并将其作为函数值返回。Promise构造函数要求传入一个函数,并带有resovlereject参数。一个成功回调函数,一个失败成功回调函数。下面是Promise对象的三个状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

三个状态的转换关系是从pending -> fulfilled或者pending -> rejected,并且状态改变以后就不会再变了。pending -> fulfilled以后会去执行传入Promise对象的resovle函数,对应的,pending -> rejected以后会去执行传入Promise对象的reject函数。

.then()

resovle函数和reject函数是怎么传进去的呢,当然就是之前说的.then(),.then()可以接收两个参数,.then(onFulfilled[, onRejected])这是官方写法,其实就是.then(resovle, reject),第一个参数是成功回调,第二个参数就是失败回调。如下⬇️

function timeout(isSuccess) {
  return new Promise((resolve, reject) => {
    if (isSuccess) {
      setTimeout(resolve, 1000)
    } else {
      reject()
    }
  })
}

timeout(true).then(() => {
  console.log('成功')
}, () => {
  console.log('失败')
});

timeout(false).then(() => {
  console.log('成功')
}, () => {
  console.log('失败')
});
复制代码

我用if语句模拟一下成功和失败的场景,这就是.then()的用法。

.catch()

刚才说了.then()的第二个参数传进去的是一个失败回调的函数,但是Promise还有一个.catch()的方法,也是用来处理失败的,例子如下⬇️:

function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
  })
}

timeout().then(() => {
  throw new Error('因为被凯多打败了,所以没当上海贼王')
}).catch((err) => {
  console.log('失败原因:', err)
});
复制代码

这时候也会输出错误信息。这时候你可能会问,那.then(resovle, reject)reject.catch(reject)有什么区别呢,下面是个人见解

.then(resovle, reject)reject.catch(reject)有什么区别

我个人认为,.then(resovle, reject)reject按就近原则,只对最近的这个异步函数进行错误处理,但是对以后的或者之前的异步函数不做处理,而.catch(reject)会捕获到全局所有链式上异步函数的错误。链式调用下面会讲到。总之就是.catch(reject)管的范围要大一些。

链式调用

Promise有一个对象链,并且这个对象链式呈流水线的模式进行作业,是因为在Promise对象对自身的onFulfilledonRejected相应器的处理中,会对其中返回的Promise对象进行处理。其中内部会将这个新的Promise对象加入到Promise对象链中,并将其暴露出来,使其继续接受新的Promise对象的加入。只有当Promise对象链中的上一个Promise对象进入成功或者失败阶段,下一个Promise对象菜户被激活,这就形成了流水线的作业模式。

这就好比一开始使用Promise改造回调地狱函数时候的样子⬇️

// 封装一层函数
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
  })
}
// 按回调函数的逻辑执行
timeout().then(() => {
    console.log(1);
    return timeout()
}).then(() => {
    console.log(2);
    return timeout()
}).then(() => {
    console.log(3);
});
复制代码

可以一层一层的传一下去,这也是厉害的地方。当链式调用中用.catch()捕获错误的时候是这样的⬇️

function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
  })
}

timeout()
  .then(() => {
    console.log(1);
    return timeout(err)
  })
  .then(() => {
    throw new Error('发生错误了')
    return timeout(2)
  })
  .catch((err) => {
    console.log('123',err)
  })
  .then(() => {
    console.log(3);
  });
复制代码

这种情况,.catch()紧跟在抛出错误的一步函数后面,会抛出错误,然后继续往下执行,但是如果.catch()是在最后,结果就完全不一样了⬇️

function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
  })
}

timeout()
  .then(() => {
    console.log(1);
    return timeout(err)
  })
  .then(() => {
    throw new Error('发生错误了')
    return timeout(2)
  })
  .then(() => {
    console.log(3);
  })
  .catch((err) => {
    console.log('123',err)
  });
复制代码

如果是这样,前面说了.catch()会捕获全局错误,但是,.catch()写在最后,抛出错误以后,函数会直接跳到.catch()然后继续往下执行,就像下面代码⬇️

function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
  })
}

timeout()
  .then(() => {
    console.log(1);
    return timeout()
  })
  .then(() => {
    console.log(11);
    throw new Error('发生错误了')
    return timeout()
  })
  .then(() => {
    return timeout(2)
  })
  .catch((err) => {
    console.log('2',err)
  })
  .then(() => {
    throw new Error('发生错误了2')
    console.log(3);
  })
  .catch((err) => {
    console.log('3',err)
  });
复制代码

上面这段代码就会直接跳过输出2的异步函数,直接走到第一个.catch(),然后再往下执行。

Promise高级

Promise.all()

这个方法真的太实用了,比如你进入首页,需要同时请求各种分类,用户信息等等信息,咱们可能需要在所有的请求都回来以后再展示页面,因为我们不能确定每个请求都要多久才能请求回来,所以这个问题一度很难解决。现在有了Promise.all()这个方法,真的太方便了,下面就是例子⬇️

// Promise.all()需要传入的就是一个数组,每一项就是每一个异步函数
function timeout(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, delay * 1000)
  })
}

Promise.all([
  timeout(1),
  timeout(3),
  timeout(5),
]).then(() => {
  console.log('都请求完毕了!')
});
复制代码

上面代码会在最大延迟的5秒后然后在执行.then()的方法,当然还有一个差不多的函数,往下看

Promise.race()

Promise.race()会监听所有的Promise对象,在等待其中的第一个进入完成状态的Promise对象。一旦有第一个Promise对象进入了完成状态,该方法返回的Promise对象便会根据这第一个完成的Promise对象的状态而改变,如下⬇️

function timeout(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, delay * 1000)
  })
}

Promise.race([
  timeout(1),
  timeout(3),
  timeout(5),
]).then(() => {
  console.log('有一个请求已经结束!')
});
复制代码

上面代码在执行1秒后就会执行.then()的方法,然后剩下的两个请求继续等待返回。
反正我也没遇到过什么使用场景,知道有这个方法就行了

只管把目标定在高峰,人家要笑就让他去笑!

写到后面有点太官方的感觉,但是又觉得很不好解释,只能堆例子来解释了,跟大佬的差距还是有一定的差距,这只是基于我现在的水平到目前为止对Promise的理解。

一句承诺,就要努力去兑现。自己选择的路,跪着也要走完。


我是前端战五渣,一个前端界的小学生。

转载于:https://juejin.im/post/5c91ccb7f265da60c95b66ef

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值