我是要成为海贼王的男人

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

悟空陪布玛找寻龙珠,一路拳打比克、斩弗利萨,生个儿子战沙鲁,最后净化布欧,只因承诺要保护地球。鸣人“有话直说,说到做到,这就是我的忍道”,一句会把佐助带回来的承诺,断臂践行。路飞要凑齐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

前端战五渣学JavaScript——Promise相关推荐

  1. 前端战五渣学JavaScript——防抖、节流和rAF

    看了<JavaScript高级程序设计>和网上的一些博客,感觉对函数节流和函数防抖的概念是反的,以下我写的关于防抖和节流的概念取决于多数人的概念吧,并且基于伦敦前端工程师David Cor ...

  2. 前端战五渣学JavaScript——call、apply以及bind

    写这篇博客之前,我想先说下今天(2019年3月28日)一直关注的一件事吧(出于凑热闹的心情--尴尬).在昨天,全球最大交友网站Github上悄然出现一个名为996.ICU的文档项目,整个项目没有代码, ...

  3. 前端战五渣学JavaScript——闭包

    就决定是你了--闭包 有不少开发人员总是搞不清匿名函数和闭包两个概念,因此经常混用.闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数内部创建另一个函数. ------ ...

  4. 前端战五渣学JavaScript——void 运算符

    最近有点忙,公司有个新项目要尽快上线,所以工作时间很长,没有太多的时间去学习和总结,所以博客也没空更新了.但是充足的工作量让自己觉得很充足,沉淀的知识也有了用武之地,还不错.今天就写写这两天突然想到的 ...

  5. 前端战五渣学JavaScript——前端数据存储

    说起前端数据存储,那就不得不提到Cookie.sessionStorage和localStorage,这是我们接触到的最熟悉的前端数据存储的单词,作为一个前端开发.前端工程师,这三个单词用没用过先不管 ...

  6. 你不知道的javascript_为什么前端要学JavaScript?JS都有哪些逆天的功能?

    相信每一个想要学习前端的人对于前端三剑客html+css+js都不会陌生,这些可以说是一个前端的标配,而在前端的世界里,没有什么是JavaScript实现不了的,你真的了解js吗? 关于JS有一句话: ...

  7. 前端战五渣学React——JSX React.createElement() React.ReactElement()源码

    最近<一拳超人>动画更新第二季了,感觉打斗场面没有第一季那么烧钱了,但是剧情还挺好看的,就找了漫画来看.琦玉老师真的厉害!!!打谁都一拳,就喜欢看老师一拳把那些上来就吹牛逼的反派打的稀烂, ...

  8. ashx获取input file 文件_前端战五渣学前端——FileReader预览本地文件

    距离上一篇博客将近一个半月了,这一个多月有点烦躁,静不下心来学习,也不知道为什么,玩的也不算太好,还感冒.可能是天气热了吧,有点点躁动.上周看了<哪吒--魔童降世>还不错,还看了新出的&l ...

  9. 前端战五渣学前端——跨域

    跨域(Cross-Origin) 这几年的工作.面试中,还遇到一个不能回避的问题,那就是跨域,一般面试的时候会问你'怎么解决跨域问题','跨域是什么','为什么会有跨域'等类似的问题.前段时间后端大佬 ...

最新文章

  1. html 表单内容怎么获取不到,jquery中formdate一直获取不到对象中的[0]的值 包括本身也是一个空的数据怎么办?...
  2. CSDN博客生成目录
  3. 人刚毕业,在小米造狗没有KPI,雷军奖励100万美金
  4. 在matlab中如何使用SVM工具箱
  5. 算法提高课-数学知识-矩阵乘法-AcWing 1303. 斐波那契前 n 项和:矩阵乘法,快速幂,线性代数
  6. 最新版FreeTextBox(版本3.1.6)在ASP.Net 2.0中使用简解(提供博客园本地下载)
  7. python 在gui中显示logging_如何在GUI中显示print()的输出python
  8. 知识点 组合数学 卡特兰数
  9. 1-概述程序构成基本元素
  10. mysql zip win10安装_mysql 8.0.16 Win10 zip版本安装配置图文教程
  11. 防爆技术在工业电子秤中的最新应用(转)
  12. 别在分答上问我问题了
  13. Mac端将本地新项目上传到github
  14. 首涂模板第四套还原经典挖片苹果cmsv10自适应主题模板
  15. 前端跨域解决方案总结
  16. JavaScript学习手册一:JS简介
  17. 初学者学习JS很吃力怎么办?到底该如何学习JS?
  18. 掌握销售谈判三大策略,开单成功率提升60%!
  19. Appium基础操作
  20. 【Android】技术调研:用代码模拟屏幕点击、触摸事件

热门文章

  1. 网线插座之一种的内部构造猜测
  2. PDCA 计划 执行 检查 反馈(处理)
  3. heic格式转换工具
  4. 计算机网络竞赛备赛安排表,2018年辽宁省职业院校技能大赛(高职组)计算机网络赛项须知...
  5. WiFi万能钥匙用户破九亿 陈大年称不想“穿越”回去
  6. SQL SERVER FOR XML PATH 用法
  7. 半监督kmeans聚类
  8. Ultra DMA CRC 错误计数 【警告】
  9. 笔记本 SSD+机械硬盘,使用中机械硬盘丢失
  10. python财经数据接口包_Tushare|免费开源财经数据接口包