资讯专栏INFORMATION COLUMN

从0开始构建自己的前端知识体系-JS-跟着规范学Promise

kelvinlee / 2696人阅读

摘要:本文仅限浏览器环境测试,环境可能会不一致状态一个实例只能处于三种状态中的一种。每次创建的实例都会处于状态,并且只能由变为或状态。可以认为在实现里与中的都为解决程序。

前言

Promise作为ES6极为重要的一个特性,将我们从无限的回调地狱中解脱出来,变为链式的编写回调,大大提高的代码的可读性。

使用Promise是极为简单的,但只停留在会使用阶段还是会让我们不知不觉踩到一些坑的。本文会结合Promise/A+规范与示例来深入学习Promise

本文较长,例子很多,末尾有一些应用 ^_^

Promise

兼容性

Promise作为是浏览器的内置函数对象,除IE不支持外所有主流版本的浏览器都是支持的,如不需支持IE可以在不引入polyfill的情况下放心食用

作用

js是单线程的,异步是通过Event Loop来实现的,那么需要一种更加友好的方式来实现我们的异步操作,而不是无限的回调嵌套

</>复制代码

  1. // no promise
  2. callback(() => {
  3. callback(() => {
  4. callback(() => {
  5. ...
  6. })
  7. })
  8. })
  9. // with promise
  10. new Promise((resolve, reject) => {
  11. }).then(() => {
  12. }).then(() => {
  13. }).then(() => {
  14. })

API

先来简单看一下Promise提供的一些API(注意区分静态方法与实例方法)

构造函数Promise

Promise是一个构造函数,可以通过new操作符来创建一个Promise实例

</>复制代码

  1. let promise = new Promise((resolve, reject) => {
  2. resolve(1)
  3. })

静态方法

Promise.resolve

Promise.reject

Promise.all

Promise.race

实例方法

Promise.prototype.then

Promise.prototype.catch

结合规范与样例

规范

官方英文原版规范

tips:

规范是规范,实现是实现,实现是按照规范来实现的,但不一定完全等同于规范。

本文仅限浏览器环境测试,node环境可能会不一致

状态

一个Promise实例只能处于pending,fulfilled,rejected三种状态中的一种。每次创建的promise实例都会处于pending状态,并且只能由pending变为fulfilledreject状态。一旦处于fulfilledrejected状态无论进行什么操作都不会再次变更状态(规范2.1)

</>复制代码

  1. // 创建一个处于pending状态的promise实例
  2. let promise1 = new Promise((resolve, reject) => {
  3. })
  4. // 调用resolve()会使promise从pending状态变为fulfilled状态
  5. let promise2 = new Promise((resolve, reject) => {
  6. resolve(1)
  7. })
  8. // 调用reject()会使promise从pending状态变为rejected状态
  9. let promise3 = new Promise((resolve, reject) => {
  10. reject(1)
  11. })
  12. // 无论如何更改resolve与reject的执行顺序,promise4始终只会处于先调用后转换的状态(状态一旦变为fulfilled或rejected后不会再次改变)
  13. let promise4 = new Promise((resolve, reject) => {
  14. resolve(1)
  15. reject(1)
  16. })

then

参数

实例方法then的回调函数接受两个参数,onFulfilledonRejected,都为可选参数(规范2.2.1)

</>复制代码

  1. // onFulfilled会在状态变为fulfilled后调用,onFulfilled参数value为resolve的第一个参数,onRejected同理。(规范2.2.2与2.2.3)
  2. let promise = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve(1)
  5. }, 1000)
  6. })
  7. promise.then((value) => {
  8. console.log(value) // 1秒后输出1
  9. }, (reason) => {
  10. })
  11. // 可被同一个promise多次调用(规范2.2.6)
  12. promise.then()
  13. promise.then((value) => {
  14. console.log(value) // 1秒后输出1
  15. })

返回值

then方法必须返回一个promise实例(规范2.2.7)

假定 promise2 = promise1.then(onFulfilled, onRejected)

</>复制代码

  1. var a = new Promise((resolve, reject) => {
  2. resolve(1)
  3. })
  4. var b = new Promise((resolve, reject) => {
  5. reject(1)
  6. })
  7. // 如果```onFulfilled``````onRejected```返回了一个value ```x```,运行promise解决程序(下一节), [[Resolve]](promise2, x) (规范2.2.7.1)
  8. var a1 = a.then(function onFulfilled(value) {
  9. return 1
  10. }, function onRejected (reason) {
  11. })
  12. var b1 = b.then(function onFulfilled(value) {
  13. }, function onRejected (reason) {
  14. return 1
  15. })
  16. // 如果```onFulfilled``````onRejected```抛出了一个exception ```e```, ```promise2```必须以e作为reason rejected (规范2.2.7.2)
  17. // 故下方a2 b2 都为状态是rejected的promise实例
  18. var a2 = a.then(function onFulfilled(value) {
  19. throw Error("test")
  20. }, function onRejected (reason) {
  21. })
  22. var b2 = b.then(function onFulfilled(value) {
  23. }, function onRejected (reason) {
  24. throw Error("test")
  25. })
  26. // 如果promise1处于fulfilled状态并且onFulfilled不是一个函数,那么promise2会以与promise1具有相同value和相同的状态, 但是与promise1并不是同一Promise实例;若为rejected状态以此类推
  27. var a3 = a.then()
  28. a3 === a // false
  29. var b3 = b.then()
  30. b3 === b // false

解决程序resolve

在规范中, promise解决程序是一个以promisevalue作为输入的抽象操作,我们表示为[[Resolve]](promise, x)

可以认为在实现里Promise.resolve()new Promise((resolve, reject) => {})中的resolve都为解决程序。规范中x对应实现中resolve的第一个参数,规范中promise在实现中等价于当前promise

可以理解为Promise.resolve(x)new Promise(resolve) => {resolve(x)}等价

</>复制代码

  1. var c = new Promise((resolve, reject) => {
  2. resolve(1)
  3. })
  4. var d = new Promise((resolve, reject) => {
  5. reject(1)
  6. })
  7. var e = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve("5s后resolve")
  10. }, 5000)
  11. })
  12. // 在返回值章节我们了解到,onFulfilled如果为函数在不抛出错误的情况下,会调用解决程序处理返回值x
  13. // 如果x是promise, 采纳他的状态(注意,但then的返回值并不与x相等)(规范2.3.2)
  14. var c1 = Promise.resolve().then(function onFulfilled() {
  15. return c
  16. })
  17. c1 === c // false
  18. // pending状态下的,只要e状态变为,e1也会改变(规范2.3.2.1)
  19. var e1 = Promise.resolve().then(function onFulfilled () {
  20. return e
  21. })
  22. var c2 = Promise.resolve()
  23. // 如果promise和x是相同的对象, 使用TypeError作为reason reject promise(规范2.3.1)
  24. var c3 = c2.then(function onFulfilled () {
  25. return c3
  26. })

概念:如果一个函数或者对象具有then属性,那么叫做thenable(例: { then: function(){} })

</>复制代码

  1. // 如果x是thenable 但x.then不是一个函数或者对象,直接返回状态为fulfilled,value为x的promise实例(规范2.3.3.4)
  2. var c4 = c1.then(function onFulfilled () {
  3. return {}
  4. })
  5. var c5 = c1.then(function onFulfilled () {
  6. return {
  7. then: 2
  8. }
  9. })
  10. // 如果x是thenable 并且x.then是一个函数,那么就会调用这个then方法执行,并且两个参数resolve与reject拥有改变返回的promise状态的权利,会按解决程序处理,如果不调用两个参数方法,则返回的promise为pending状态,值得一提的是,调用then方法的时候回以返回对象x作为thenthis绑定调用(规范 2.3.3.3)
  11. var c6 = c1.then(function onFulfilled () {
  12. return {
  13. test: "test",
  14. then: function (resolve, reject) {
  15. console.log(this.test)
  16. resolve("thenable")
  17. }
  18. }
  19. })
  20. var c7 = c1.then(function onFulfilled () {
  21. function x () {}
  22. x.test = "test"
  23. x.then = function (resolve, reject) {
  24. console.log(this.test)
  25. resolve("thenable")
  26. }
  27. return x
  28. })
  29. var c8 = c1.then(function onFulfilled () {
  30. return {
  31. test: "test",
  32. then: function (resolve, reject) {
  33. console.log(this.test)
  34. reject("thenable")
  35. }
  36. }
  37. })
  38. // 返回pending状态的promise
  39. var c9 = c1.then(function onFulfilled () {
  40. return {
  41. then: function () {}
  42. }
  43. })
  44. // 如果x不是对象也不是函数,则返回状态为fulfilled的promise,value为x
  45. var c10 = c1.then(function onFulfilled () {
  46. return "hehe"
  47. })

综上可以总结几点

</>复制代码

  1. 1. ```new Promise, promise.then, Promise.resolve()```都会返回promise实例
  2. 2. Promise状态一旦改变不可再更改
  3. 3. ```then```方法返回对象的不同会导致不同的结果,如果意外返回了thenable有可能会造成意想不到的结果
应用

封装一个异步请求

</>复制代码

  1. var promiseXHR = new Promise((resolve, reject) => {
  2. var xhr = new XMLHttpRequest()
  3. xhr.open("GET", "http://www.baidu.com", true)
  4. xhr.onreadystatechange = function () {
  5. if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
  6. resolve(xhr.response)
  7. } else {
  8. reject()
  9. }
  10. }
  11. xhr.send(data)
  12. })

同时请求按序处理

</>复制代码

  1. // 假设有5个章节的数据,我们需要分别获取并读取到c中,利用promise和es6数组api我们可以写出简洁优雅的代码完成这个需求
  2. var list = ["Chapter1", "Chapter2", "Chapter3", "Chapter4", "Chapter5"]
  3. var getData = function (key) {
  4. return new Promise((resolve, reject) => {
  5. // 模拟一些返回时间不相同的异步操作
  6. setTimeout(() => {
  7. resolve(key)
  8. }, 4000 * Math.random())
  9. })
  10. }
  11. var c = ""
  12. list.map(i => getData(i))
  13. .reduce((accumulator, current) => {
  14. console.log(accumulator)
  15. return accumulator.then(() => {
  16. return current
  17. }).then(value => {
  18. c+= value
  19. })
  20. }, Promise.resolve(""))

catch

明明在我们日常工作中常用到的catch方法,为什么到现在还一点都没有提到呢?

因为catch方法就是then方法封装出来的语法糖而已,因为如果只想捕获错误,还每次都要书写then的第一个参数,真的是麻烦至极,现在我们来写一个自己的catch

</>复制代码

  1. Promise.prototype.mycatch = function (cb) {
  2. return this.then(1, function onRejected (reason) {
  3. return cb(reason)
  4. })
  5. }

// 用到了规范中如果onFulfilled不是一个函数,则忽略,并使用原先promise状态与value

finally

有的浏览器实现了finally,而有的并没有,从需求上来讲finally只不过是最终要执行一个函数,我们需要把应该有的状态或者异常都继续传递,不受其影响。执行的函数与promise的value无任何关系

</>复制代码

  1. Promise.prototype.myfinally = function (cb) {
  2. return this.then(function onFulfilled (value) {
  3. return Promise.resolve(cb()).then(() => value)
  4. }, function onRejected (reason) {
  5. return Promise.resolve(cb()).then(() => {
  6. throw reason
  7. })
  8. })
  9. }

后记

通过阅读规范并写demo进行验证测试,对Promise的理解更加深入了,也能更好的使用它了
如果喜欢可以star一下,会不断更新github地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94387.html

相关文章

  • 【全文】狼叔:如何正确习Node.js

    摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...

    Edison 评论0 收藏0
  • 【全文】狼叔:如何正确习Node.js

    摘要:感谢大神的免费的计算机编程类中文书籍收录并推荐地址,以后在仓库里更新地址,声音版全文狼叔如何正确的学习简介现在,越来越多的科技公司和开发者开始使用开发各种应用。 说明 2017-12-14 我发了一篇文章《没用过Node.js,就别瞎逼逼》是因为有人在知乎上黑Node.js。那篇文章的反响还是相当不错的,甚至连著名的hax贺老都很认同,下班时读那篇文章,竟然坐车的还坐过站了。大家可以很...

    fengxiuping 评论0 收藏0
  • 一名【合格】前端工程师自检清单

    摘要:在他的重学前端课程中提到到现在为止,前端工程师已经成为研发体系中的重要岗位之一。大部分前端工程师的知识,其实都是来自于实践和工作中零散的学习。一基础前端工程师吃饭的家伙,深度广度一样都不能差。 开篇 前端开发是一个非常特殊的行业,它的历史实际上不是很长,但是知识之繁杂,技术迭代速度之快是其他技术所不能比拟的。 winter在他的《重学前端》课程中提到: 到现在为止,前端工程师已经成为研...

    罗志环 评论0 收藏0
  • 一名【合格】前端工程师自检清单

    摘要:在他的重学前端课程中提到到现在为止,前端工程师已经成为研发体系中的重要岗位之一。大部分前端工程师的知识,其实都是来自于实践和工作中零散的学习。一基础前端工程师吃饭的家伙,深度广度一样都不能差。开篇 前端开发是一个非常特殊的行业,它的历史实际上不是很长,但是知识之繁杂,技术迭代速度之快是其他技术所不能比拟的。 winter在他的《重学前端》课程中提到: 到现在为止,前端工程师已经成为研发体系...

    isaced 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<