promise的超详细讲解

132 阅读8分钟
1. 异步请求处理方式
/**
 * 
 * 这种回调的方式有很多弊端:
 * 1> 如果是我们自己封装的requestData,那么我们在封装的时候必须要设计好callback名称,并且使用好
 * 2> 如果我们使用的是别人封装的requestData或者一些第三方库,那么我们必须去看别人的源码或者文档,才知道它这个函数需要怎么去获取到结果
 */

function requestData (url, successCallback, failtureCallback) {
    // 模拟网络请求
    setTimeout(() => {
        // 拿到请求结果
        if (url === 'arielBo') {
            // 成功
            let names = ['aa', 'bb', 'nba'];
            successCallback(names)
        } else { // 否则请求失败
            // 失败
            let errMessage = "请求失败,url错误";
            failtureCallback(errMessage);
        }

    }, 3000);
}

// main.js
requestData("arielBo1", res => {
    console.log(res, 'res---');
}, err => {
    console.log(err)
})

更好的解决办法是Promise

2. 我们来看一下Promise的API是怎么样的
  1. Promise是一个类,可以翻译成 承诺、许诺 、期约;

  2. 当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;

  3. 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor

  • 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
  • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
  • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
3. promise的基本使用

image.png

上面Promise使用过程,我们可以将它划分成三个状态:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝;

  • 当执行executor中的代码时,处于该状态;

  • 已兑现(fulfilled): 意味着操作成功完成;

  • 执行了resolve时,处于该状态;

  • 已拒绝(rejected): 意味着操作失败;

  • 执行了reject时,处于该状

function foo() {
  // Promise
  return new Promise((resolve, reject) => {
    resolve("success message")
    // reject("failture message")
  })
}

// main.js
const fooPromise = foo()
// then方法传入的回调函数两个回调函数:
// > 第一个回调函数, 会在Promise执行resolve函数时, 被回调
// > 第二个回调函数, 会在Promise执行reject函数时, 被回调
fooPromise.then((res) => {
  console.log(res)
}, (err) => {
  console.log(err)
})

// // catch方法传入的回调函数, 会在Promise执行reject函数时, 被回调
fooPromise.catch(() => {

})


// 传入的这个函数, 被称之为 executor
// > resolve: 回调函数, 在成功时, 回调resolve函数
// >reject: 回调函数, 在失败时, 回调reject函数
// const promise = new Promise((resolve, reject) => {
//   // console.log("promise传入的函数被执行了")
//   // resolve()
//   reject()
// })

// promise.then(() => {

// })

// promise.catch(() => {

// })


// 钩子函数: hook
function foo(fn) {
  fn()
}

foo(() => {

})
4. 异步请求的promise
js
// request.js
function requestData(url,) {
  // 异步请求的代码会被放入到executor中
  return new Promise((resolve, reject) => {
    // 模拟网络请求
    setTimeout(() => {
      // 拿到请求的结果
      // url传入的是coderwhy, 请求成功
      if (url === "coderwhy") {
        // 成功
        let names = ["abc", "cba", "nba"]
        resolve(names)
      } else { // 否则请求失败
        // 失败
        let errMessage = "请求失败, url错误"
        reject(errMessage)
      }
    }, 3000);
  })
}

// main.js
const promise = requestData("coderwhy")
promise.then((res) => {
  console.log("请求成功:", res)
}, (err) => {
   console.log("请求失败:", err)
})

5. promise的三种状态
// const promise = new Promise((resolve, reject) => {

// })

// promise.then(res => {

// }, err => {

// })

// 完全等价于下面的代码
// 注意: Promise状态一旦确定下来, 那么就是不可更改的(锁定)
new Promise((resolve, reject) => {
  // pending状态: 待定/悬而未决的
  console.log("--------")
  reject() // 处于rejected状态(已拒绝状态)
  resolve() // 处于fulfilled状态(已敲定/兑现状态)
  console.log("++++++++++++")
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err)
})

6. Promise的resolve参数
/**
 * resolve(参数)
 *  1> 普通的值或者对象  pending -> fulfilled
 *  2> 传入一个Promise
 *    那么当前的Promise的状态会由传入的Promise来决定
 *    相当于状态进行了移交
 *  3> 传入一个对象, 并且这个对象有实现then方法(并且这个对象是实现了thenable接口)
 *    那么也会执行该then方法, 并且又该then方法决定后续状态
 */

// 1.传入Promise的特殊情况
// const newPromise = new Promise((resolve, reject) => {
//   // resolve("aaaaaa")
//   reject("err message")
// })

// new Promise((resolve, reject) => {
//   // pending -> fulfilled
//   resolve(newPromise)
// }).then(res => {
//   console.log("res:", res)
// }, err => {
//   console.log("err:", err)
// })

// 2.传入一个对象, 这个兑现有then方法
new Promise((resolve, reject) => {
  // pending -> fulfilled
  const obj = {
    then: function(resolve, reject) {
      // resolve("resolve message")
      reject("reject message")
    }
  }
  resolve(obj)
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err)
})

// eatable/runable
const obj = {
  eat: function() {

  },
  run: function() {

  }
}

7. Promise对象方法-then
// Promise有哪些对象方法
// console.log(Object.getOwnPropertyDescriptors(Promise.prototype))

const promise = new Promise((resolve, reject) => {
  resolve("hahaha")
})

// 1.同一个Promise可以被多次调用then方法
// 当我们的resolve方法被回调时, 所有的then方法传入的回调函数都会被调用
// promise.then(res => {
//   console.log("res1:", res)
// })

// promise.then(res => {
//   console.log("res2:", res)
// })

// promise.then(res => {
//   console.log("res3:", res)
// })

// 2.then方法传入的 "回调函数: 可以有返回值
// then方法本身也是有返回值的, 它的返回值是Promise

// 1> 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined), 那么这个普通的值被作为一个新的Promise的resolve值
// promise.then(res => {
//   return "aaaaaa"
// }).then(res => {
//   console.log("res:", res)
//   return "bbbbbb"
// })

// 2> 如果我们返回的是一个Promise
// promise.then(res => {
//   return new Promise((resolve, reject) => {
//     setTimeout(() => {
//       resolve(111111)
//     }, 3000)
//   })
// }).then(res => {
//   console.log("res:", res)
// })

// 3> 如果返回的是一个对象, 并且该对象实现了thenable
promise.then(res => {
  return {
    then: function(resolve, reject) {
      resolve(222222)
    }
  }
}).then(res => {
  console.log("res:", res)
})

8. Promise 对象方法-catch
// const promise = new Promise((resolve, reject) => {
//   resolve()
//   // reject("rejected status")
//   // throw new Error("rejected status")
// })

// 1.当executor抛出异常时, 也是会调用错误(拒绝)捕获的回调函数的
// promise.then(undefined, err => {
//   console.log("err:", err)
//   console.log("----------")
// })

// 2.通过catch方法来传入错误(拒绝)捕获的回调函数
// promise/a+规范
// promise.catch(err => {
//   console.log("err:", err)
// })
// promise.then(res => {
//   // return new Promise((resolve, reject) => {
//   //   reject("then rejected status")
//   // })
//   throw new Error("error message")
// }).catch(err => {
//   console.log("err:", err)
// })


// 3.拒绝捕获的问题(前面课程)
// promise.then(res => {

// }, err => {
//   console.log("err:", err)
// })
// const promise = new Promise((resolve, reject) => {
//   reject("111111")
//   // resolve()
// })

// promise.then(res => {
// }).then(res => {
//   throw new Error("then error message")
// }).catch(err => {
//   console.log("err:", err)
// })

// promise.catch(err => {

// })

// 4.catch方法的返回值
const promise = new Promise((resolve, reject) => {
  reject("111111")
})

promise.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
  return "catch return value"
}).then(res => {
  console.log("res result:", res)
}).catch(err => {
  console.log("err result:", err)
})

9. Promise对象方法-finally
const promise = new Promise((resolve, reject) => {
  // resolve("resolve message")
  reject("reject message")
})

promise.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
}).finally(() => {
  console.log("finally code execute")
})
10. 类方法Promise.resolve
1.普通的值 <对象转成promise>
const promise = Promise.resolve({name: "why"})
相当于
const promise2 = new Promise((resolve, reject) => {
    resolve({name: "why"});
})

image.png

传入promise
const promise = Promise.resolve(new Promise((resolve, reject) => {
    resolve("11111");
}))
promise.then(res => {
    console.log("res:", res);
})

image.png

11. promise的方法-reject
// 注意: 无论传入什么值都是一样的
const promise = Promise.reject(new Promise(() => {}))

promise.then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
})
12. Promise类方法 - all

另外一个类方法是Promise.all:

它的作用是将多个Promise包裹在一起形成一个新的Promise;

新的Promise状态由包裹的所有Promise共同决定:

当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值

组成一个数组;

当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;a

// 创建多个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11111)
  }, 1000);
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(22222)
  }, 2000);
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(33333)
  }, 3000);
})

// 需求: 所有的Promise都变成fulfilled时, 再拿到结果
// 意外: 在拿到所有结果之前, 有一个promise变成了rejected, 那么整个promise是rejected
Promise.all([p2, p1, p3, "aaaa"]).then(res => {
  console.log(res)
}).catch(err => {
  console.log("err:", err)
})

image.png

// 创建多个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11111)
  }, 1000);
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(22222)
  }, 2000);
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject({
            message: "你是错误的信息"
    })
  }, 3000);
})

// 需求: 所有的Promise都变成fulfilled时, 再拿到结果
// 意外: 在拿到所有结果之前, 有一个promise变成了rejected, 那么整个promise是rejected
Promise.all([p2, p1, p3, "aaaa"]).then(res => {
  console.log(res)
}).catch(err => {
  console.log("err:", err)
})

image.png

13 Promise allSettled

所有的Promise对象均出现结果(无论成功或失败)后才会执行allSettled中的then回调(只会进入then回调)

all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。

那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;

在ES11(ES2020)中,添加了新的API Promise.allSettled:

该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;

并且这个Promise的结果一定是fulfilled的;

allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;

p这个对象中包含status状态,以及对应的value值;

// 创建多个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11111)
  }, 1000);
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(22222)
  }, 2000);
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(33333)
  }, 3000);
})

// allSettled
Promise.allSettled([p1, p2, p3]).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

image.png 使用场景

使用场景: 在Promise.allSettled出现以前,对于需要发送一组ajax请求都是使用Promise.all去处理,待所有参数实例都resolve则可以继续执行下一步操作,但这样会有个弊端,若是有一个参数实例reject,则直接会进入到catch里面,即使其它实例参数resolve也是如此,Promise.all方法resolve的条件在于参数数组中的所有Promise实例都需要resolve才可以,这样显然在一些业务中不适用的,假如一个模块需要显示三部分内容,每一部分内容都有一个返回Promise实例的接口,如果使用Promise.all需要三个接口都成功返回数据才可以,如果有一个接口挂掉了,则另外两个接口返回的数据不能被获取到,因为此时已经进入到了catch方法,无法在成功回调的函数里面操作数据,渲染界面等。此时Promise.allSettled便派上用场了。无论参数实例是否reject,最终Promise.allSettled内部都会resolve,只不过会添加一个状态status来记录对应的参数实例是否执行成功。我们可以依据这个状态去过滤掉rejected的数据,只操作fulfilled的数据,就会得到我们想要的业务逻辑了。