1. 什么是 Promise
一项技术不会凭空产生, 都是为了解决某些实际的问题而出现. 了解技术产生的背景, 可以让我们更好的知道他擅长解决什么问题, 哪些场景我们可以利用他来解决. 那么就让我们一步一步来揭开 promise 神秘的面纱.
1.1. 什么是 promise
首先我们来了解一下 promise.promise 英语的意思是: 诺言. Promise 是抽象异步处理对象及其对其进行各种操作的组件. 用大白话说, promise 就是用来解决异步回调问题的.
1.2.promise 解决了什么问题
在没有 promise 之前, 前端处理 ajax 请求通常是这样的:
- function fetchData (callback) {
- $.ajax({
- type: 'GET',
- url:'xxx',
- data: data,
- success: function (res) {
- callback(res) // 回调函数
- }
- })
- }
复制代码
这只是处理一个 ajax 请求, 可很多时候, 我们回面临第一个请求回来的结果, 是第二次请求的参数, 或者我们想一个极端的例子, 前一次请求的结果是第二次请求的参数, 那么我们的代码可能是这样的:
- function fetchData (callback) {
- $.ajax({
- type: 'GET',
- url: 'xxx',
- data:data,
- success: function (res) {
- if (res.data) {
- const params1 = res.data;
- $.ajax({
- type:'GET',
- url: 'xxx',
- data: params1,
- success: function (res) {
- if (res.data) {
- const params2 = res.data
- $.ajax({
- type:'GET',
- url: 'xxx',
- data: paramsw,
- success: function (res) {
- if (res.data) {
- const params3 = res.data;
- ......
- }
- }
- })
- }
- }
- })
- }
- }
- })
- }
复制代码
上面这个就是回调地狱. 代码的可读性, 可维护性大打折扣. promise 就是为他而生的, 通过 promise 我们可以像写同步那样写异步方法. 同样是上面的场景我们完全可以用另一种方法:
- function fetchData (url, data) {
- return new Promise((resolve, reject) => {
- $.ajax({
- type: 'GET',
- url:url,
- data: data,
- success: function (res) {
- resolve(res)
- },
- fail: function (err) {
- reject(res)
- }
- })
- })
- }
- fetchData(url,data).then(res => {
- const url1 = 'yyyy';
- const data1 = res.data;
- return fetchData(url1,data1)
- }).then(res => {
- const url2 = 'zzz';
- const data2 = res.data
- return fetchData(url2, data2)
- }).then(res => {
- const url3 = 'wwww';
- const data3 = res.data;
- render()
- }).catch(err => {
- console.log(err)
- })
复制代码
这样似乎美观了那么一丢丢, 但是不要着急, 我们一步一步来.
2.promise 的实际应用
上一章我们主要探讨了 promise 的背景知识, 简单的知道了 promise 使用的场景, 这章我们就来跟进一步的学习 promise
2.1 promised 的状态
promise 有三种状态:
- pending
- fulfilled
rejected 如下图:
promise 对象的状态, 从 Pending 转换为 Fulfilled 或 Rejected 之后, 这个 promise 对象的状态就不会再发生任何变化.
2.2 promise 是异步的么?
如下代码: 你觉得输出的结果是什么呢?
- var promise = new Promise(function (resolve){
- console.log("1"); //1
- resolve(2);
- });
- promise.then(function(value){
- console.log(value); // 2
- });
- console.log("3"); // 3
复制代码
实际上执行上面的代码会输出下面的内容
1
3
2
复制代码
你会很好奇为什么会是这样?
由于 JavaScript 代码会按照文件的从上到下的顺序执行, 所以最开始 <1> 会执行, 然后是 resolve(2); 被执行. 这时候 promise 对象的已经变为确定状态, FulFilled 被设置为了 2 .
由于 promise.then 执行的时候 promise 对象已经是确定状态, 从程序上说对回调函数进行同步调用也是行得通的.
但是即使在调用 promise.then 注册回调函数的时候 promise 对象已经是确定的状态, Promise 也会以异步的方式调用该回调函数, 这是在 Promise 设计上的规定方针.
因此 <3> 会最先被调用, 最后才会调用回调函数 <2> .
2.2 promise 的链式调用
在前面的 < 1.2 > 中我们简单介绍了下 promise 的链式调用, 在这里我们再次深入理解一下 看下面的代码
- function task () {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve('task')
- },1000)
- })
- }
- task().then((res) => {
- console.log(res)
- return 'taskB'
- }).then(res => {
- console.log(res)
- return 'taskC'
- }).then(res => {
- console.log(res)
- throw new Error()
- }).catch(e => {
- console.log(e)
- return 'taskD'
- }).then(res => {
- console.log(res)
- })
复制代码
哈哈, 那么问题来啦? 上面会输出什么呢?
哈哈, 是不是很困惑 catch 后怎么又进入 then() 了呢? 实际上前面我们已经说过, promise 有三种状态 pending,fulfilled,rejected, 一旦从 pending 态到 fulFilled, 或者从 pending 态到 rejected, 其状态都是不可逆的. 因此,.then() 中每次 return 出去的都是一个新的 promise, 而不是 this. 有图有真相 (虽然图是我盗的, 说明问题就好):
那么对于异常情况下 promise 的流程是这个样子的:
3. 尝试写出符合 Promse/A + 规范的 promise
前两小节介绍了 promise 的背景, 及 promise 的更深入的学习, 这节我们通过自己动手实现一个符合 promise/A + 规范的 promise 来彻底弄懂 promise
3.1 promise 构造函数
通过 2.2 的探究我们知道当你创建一个 new Promise() 的时候, 实际上在 new Promise() 内部是同步执行的, 也就是说
- const promise = new Promise((resolve,reject) => {
- console.log(1)
- })
复制代码
当代码执行到 console.log(1) 的时候, 1 会被立马打印出来. 也就是说, promise 的构造函数中有一个 executor 的函数, 他会立马执行. 因此:
- function Promise (executor) {
- executor()
- }
复制代码
我们有向 executor 中传入了两个函数, resolve 和 reject, 因此我们来进一步完善我们的构造函数:
- function Promise(executro) {
- function resolve () {}
- function reject () {}
- executor(resolve, reject)
- }
复制代码
我们知道 promise 中有三种状态, 那么我们需要一个属性来控制这个状态, 我们添加一个 status 的属性吧:
- function Promise(executor) {
- let self = this;
- self.status = 'pending' // 初始状态为 pending
- function resolve() {}
- function reject() {}
- }
复制代码
当执行 resolve 函数的时候, status 的状态应该从 penging 态转为 resolved, 同时存储 resolve 函数的值, 同样的当执行 reject 函数的时候, status 的状态应该从 pengding 态转为 rejected, 同时我们存储 reject 的值, 代码入下:
- function Promise (executor) {
- let self = this;
- self.status = 'pending'
- self.value = undefined
- self.reason = undefined
- self.onResolved = [] // 3.2 小节添加
- self.onRejected = [] // 3.2 小姐添加
- function resolve (value) {
- if (self.status === 'pending') {
- self.status = 'resolved'
- self.value = value
- self.onResolved.forEach(fn => fn())
- }
- }
- function reject (reason) {
- if (self.status === 'pending') {
- self.status = 'rejected'
- self.reason = reason
- self.onRejected.forEach(fn => fn())
- }
- }
- // 处理下异常
- try {
- executor(resolve, reject)
- } catch (e) {
- reject(e)
- }
- }
复制代码
nice ^_^ 现在我们的 Promise 的构造函数就基本能满足我们的需求啦.
3.2 promise then 方法
Promise/A + 规范中, 每个 promise 都有一个 then 方法, 该方法返回的还是一个 promise. 这个 then 方法实在实例上调用, 因此该 then 方法应该在 promise 的原型上, 同样的我们需要根据 promsie 的状态来进行相应的处理, 不同的是, 当 status 是 pending 态的时候我们要收集 resolve 和 reject, 同时在构造函数中增加相应的处理, 代码如下:
- Promise.prototype.then = function (onfulfilled, onrejected) {
- // 这里坐下简单的处理
- onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : val => val
- onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err}
- let promise2 = new Promise((resolve, reject) => {
- if (self.status === 'resolved') {
- // onfulfilled(self.value) 3.2
- let x = onfulfilled(self.value) // 3.3
- resolvePromise(promise2, x, resolve, reject) // 3.3
- }
- if (self.status === 'rejected') {
- // onrejected(self.reason) 3.2
- let x = onrejected(self.reason) // 3.3
- resolvePromise(promise2, x, resolve, reject) // 3.3
- }
- if (self.status === 'pending') {
- self.onResolved.push(function () {
- setTimeout(() => {
- // onfulfilled(self.value) 3.2
- let x = onfulfilled(self.value) // 3.3
- resolvePromise(promise2, x, resolve, reject) // 3.3
- },0)
- })
- sef.onRejected.push(function () {
- setTimeout(() => {
- // onrejected(self.reason) 3.2
- let x = onrejected(self.reason) // 3.3
- resolvePromise(promise2, x, resolve, reject) // 3.3
- }, 0)
- })
- }
- })
- return promise2
- }
复制代码
我们的 then 函数就实现啦, 最重要的就是对 pending 态的处理
3.3 onfullfiled 和 onrejected 返回结果的处理
我们在 3.2 小节留了一个坑, 你会发现, 新的 Promise 构造函数中传入的 resolve,reject 好像没有用到啊. 那么这小节就是要填这个坑的. 那我们会发现 then 方法中的 onResolved 和 onRejected 实际上分别是在 Promise 构造函数中的 resolve 和 reject 中处理的. 实际上我们会面临以下集中情况:
- let promise = new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve(1)
- },0)
- })
- promise.then((res) => {
- return res
- },(err) => {
- // 直接 reject
- reject(err)
- }).then(res => {
- // 1. 直接返回一个变量
- return res
- // 2. 返回一个对象或者函数
- return obj
- // 3. 循环引用
- })
复制代码
因此我们对 3.2 的代码改造, 我直接写在 3.2 的代码中加上 3.3 的标记, 我们还要给出对不同返回值的解析处理
- function resolvePromise(promise2, x, resolve, reject) {
- // 判断是否循环引用
- if (x === promise2) {
- return reject(new TypeError('循环引用'))
- }
- // 判断是否为对象或者函数
- if (x != null && (typeof x === 'object' || typeof x === 'function')) {
- try {
- let then = x.then
- // 判断是否为 promise
- if (typeof then === 'function') {
- then.call(x, (y) => {
- resolvePromise(promise2, y, resolve, reject)
- }, (e) => {
- reject(e)
- })
- } else {
- resolve(x)
- }
- } catch (e) {
- reject(e)
- }
- } else {
- resolve(x)
- }
- }
复制代码
这下我们的 promise 就完成啦
来源: https://juejin.im/post/5b854f22e51d4538e567b7de