Skip to content

Latest commit

 

History

History
490 lines (389 loc) · 14.7 KB

0001.md

File metadata and controls

490 lines (389 loc) · 14.7 KB

对写文章这件事已经阁了3个月了,工作太忙很难抽出大块时间来总结写作。现在稍微闲赋些,准备好好对以前的技术做一下总结。为什么要写关于 Promise 呢?有以下 3 点

  1. 之前面试时候,面试官问 Promise 问的比较多,实在要对它进行一个好好总结了,也是 JavaScript 比较难懂的一个技术点,平时我面试别人时候,也喜欢问 Promise 相关的,保证他在工作中能够熟练运用,自己能封装axios,能控制比较复杂的同步异步流程等...

  2. 这三个月的工作中,自己也总是遇到关于 Promise 的一些运用场景,有时候又比较疑惑一些地方,也是为自己总结一下 Promise,以后在工作中更加运用自如。

    接下来我从最基础的同步异步谈起,再通过图示谈他的语法,如何自己写一个Promise,再谈一下我在工作中遇到的 Promise,最后谈一下 新的语法 asyncawait Promise 的结合和比较

  3. 网上关于Promise的介绍大多数是一些语法的介绍,大多没有结合场景和同步异步相关理解来谈。对于理解Promise的前世今生还是不够深入,不够具体。

理解同步异步

理解JS执行原理

console.log(1)
setTimeout(() => {
	console.log(2)
}, 1000)
console.log(3)

如果延迟时间为0

console.log(1)
setTimeout(() => {
	console.log(2)
}, 0)
console.log(3)

console.log(1)
setTimeout(() => {
	console.log(2)
}, 2000)
console.log(3)
setTimeout(() => {
	console.log(4)
}, 1000)
console.log(5)
setTimeout(() => {
	console.log(6)
}, 0)
console.log(7)
setTimeout(() => {
	console.log(8)
}, 1000)
console.log(9)

这里可以发现几点

  • 1,3,5,7,9 是同步代码,6,4,8,2是异步代码,同步先于异步执行
  • 单看同步代码,按位置顺序依次执行
  • 单看异步代码,不同执行时间,越长越靠后执行。相同执行时间,按位置顺序依次执行

这里我们总结下同步与异步的规律

  • 代码中存在异步函数,不管需要时间多久,都要在同步完成后执行
  • 同步按位置顺序执行;异步按时间长短执行,同样时间长短时,按位置顺序执行

这是一个怎样的机制,JS引擎又是如何处理的呢?

我找到了一张这样的图

首先来解释一下这个图:这是JS的事件循环,分为3个步骤

  1. 引擎将同步、异步函数按次序载入到执行栈里面
  2. 执行栈的将异步函数放入异步线程里面
  3. 线程根据任务完成时间依次推入任务队列中执行

如何处理异步呢, 最初的方法是使用回调

理解回调

引入一下知乎上的高赞回答 现在我们把它用代码写出来看看

function fetchSomething() {
  console.log('去取货!')
}

function buySomething(callback) {
  console.log('没货了!')
  setTimeout(() => {
    console.log('有货了!')
    callback()
  }, 1000)
}

buySomething(fetchSomething)

用简单的语言描述就是:将一个函数作为参数传给另一个函数调用

要搞清楚的一点是,回调和异步没有直接的关系,也可以同步回调,也可以异步回调。我们是通过回调这个机制来实现异步的操作,例如

// 这个一个请求的异步函数,来实现异步操作
function getSomePeopleName(params, callback) {
  setTimeout(() => {
    let data

    if (params === 'suo') {
      data = 'yue'
    }
    
    console.log(callback(data))
  }, 1000)
}
console.log(getSomePeopleName('suo', (data) => '我是回调函数:' + data))

回调函数的结果一定是在回调函数里面,如果我是这样一个流程呢?

A -> B -> C -> D

function A(callback) {
  console.log('开始执行A')
  setTimeout(() => {
      callback('A')
  }, 500)
}

function B(callback) {
  console.log('开始执行B')
  setTimeout(() => {
      callback('B')
  }, 400)
}

function C(callback) {
  console.log('开始执行C')
  callback('C')
}

function D(callback) {
  console.log('开始执行D')
  setTimeout(() => {
      callback('D')
  }, 200)
}

A((a) => {
  B((b) => {
    C((c) => {
      D((d) => {
        console.log(a, b, c, d)
      })
    })
  })
})

由上可知,回调函数有几个特定

  • 使用回调函数嵌套,回调函数一定在上一个回调之后执行,用于可以控制流程,不会出现异步函数在执行顺序的混乱,即使同步异步函数混合
  • 回调函数解构嵌套,代码不够清晰,俗称回调地狱

那么有没有更好的异步操作机制呢?这里我们就要谈到 Promise

深入 Promise

为什么要有Promise?

ES6 标准中 Promise 语法

  • Promise是一个构造函数

我们写一个简单的 Promise 看看

new Promise(() => {})

// 有一个参数executor的构造函数
// executor 也是一个函数,具有两个参数 resolve, reject 是两个回调函数,当执行到回调函数时,会执行
const isResolve = true
new Promise((resolve, reject) => {
	if (isResolve) {
		resolve()
	} else {
		reject()
	}
})

可以看到 Promise 的状态从pending变成resolved

如果将 isResolve 置为 false 呢?

Promise 状态从 pending 变成 rejected 同时抛出一个异常,并且异常未被捕获,所以我们写Promise时候一定要加上catch 来捕获异常

const isResolve = false
new Promise((resolve, reject) => {
	if (isResolve) {
		resolve()
	} else {
		reject('我拒绝你')
	}
}).catch((err) => {
  console.log('捕获异常', err)
})

现在我们把异常捕获到了,但是神奇的事情发生了,异常状态应该是rejected,怎么变成 resolved了呢? 我们或许很纳闷,这个放在后面讨论,我们先看看 then 怎么处理的

const isResolve = true
new Promise((resolve, reject) => {
	if (isResolve) {
		resolve('通过')
	} else {
		reject('我拒绝你')
	}
}).then((res) => {
  console.log('resolve', res)
}, (res) => {
  console.log('reject', res)
})

isResolve = false

我们可以看到 then 是怎么处理的

  1. 如果 前面resolve()调用,Promise状态为 resolvedthen则执行第一个回调函数参数
  2. 如果 前面reject()调用,Promise状态为 rejectedthen则执行第二个回调函数参数
  3. then 执行回调函数之后Promise 的 状态都为 resolved

下面进一步验证下

const isResolve = false
new Promise((resolve, reject) => {
	if (isResolve) {
		resolve('通过')
	} else {
		reject('我拒绝你')
	}
}).catch(err => {
  console.log('我捕获到了', err)
}).then((res) => {
  console.log('resolve', res)
}, (res) => {
  console.log('reject', res)
})

果然呢,catch之后,then还会去执行,并且resolve 的参数为 undefined

问题是现在我们如何控制一个流程呢?Promise并不能直接给我们进行长流程的分支选择

下面有一个简单流程

尝试使用Promise去控制流程

const process = [102, 204]
new Promise((resolve, reject) => {
  if (process[0] === 101) {
     setTimeout(() => {
      console.log(101)
      resolve(101)
    }, 1000)
  } else {
    setTimeout(() => {
      console.log(102)
      reject(102)
    }, 1000)
  }
}).then(() => {
  if (process[1] === 201) {
    setTimeout(() => {
      console.log(201)
    }, 500)
  } else {
    setTimeout(() => {
      console.log(202)
    }, 500)
  }
}, () => {
    if (process[1] === 203) {
    setTimeout(() => {
      console.log(203)
    }, 500)
  } else {
    setTimeout(() => {
      console.log(204)
    }, 500)
  }
}).catch((err) => {
  console.log(err)
})

下面我们试试更加复杂的流程

Promise 调用链:then 的作用

单个Promise没办法控制长流程,我们怎么将Promise形成一个控制链呢,需要理解then的返回在其中起到的作用

promise.then(onFulfilled, onRejected) 
  • 接收两个参数,onFulfilledpromiseresolved 状态被调用

  • 接收两个参数,onRejectedpromiserejected 状态被调用

  • 返回值比较复杂,下面用表格列出来

    序号 场景 返回的Promise状态改变为 回调函数参数值
    1 返回1 个值 resolved 返回值
    2 没有返回 resolved undefined
    3 抛出错误 rejected 错误
    4 resolved的Promise resolved 返回的Promise回调的参数值
    5 rejected的Promise rejected 返回的Promise回调的参数值
    6 pending的Promise pending 返回的Promise回调的参数值

我们可以看出想要控制then 的后续流程,必须通过这 6 种情况来控制 下面来测试一下 这 6 种情况是否符合我们的预期

  1. 返回一个值
new Promise((resolve, reject) => {
		resolve('通过')
}).then((res) => {
  console.log('1', res)
  return res // then的返回值
}).then((res) => {
  console.log('2', res)
})

2. 不返回

3. 抛出错误

4. resolved的Promise

5. rejected的Promise

6. pending的Promise

看图写代码

分析特点:

这样就比较复杂了,还要考虑暂停的问题,但是根据我们上面测试到的,通过then的返回值控制流程也没有想象那么难

task([101, 201])
function task(testPath) {
  console.log('开始测试', testPath)
  new Promise((resolve, reject) => {
    console.log('000')
    if (101 === testPath[0]) {
      resolve('101')
    } else {
      reject('102')
    }
  }).then((res) => {
    console.log('201', '上一个返回:' + res)
    return new Promise(() => {})
  }, (res) => {
    if (202 === testPath[1]) {
      console.log('202',  '上一个返回:' + res)
      return '202'
    } else {
      console.log('203',  '上一个返回:' + res)
      throw '203'
    }
  }).then((res) => {
      console.log('301',  '上一个返回:' + res)
      return Promise.resolve('301')
  }, (res) => {
       console.log('302',  '上一个返回:' + res)
      return Promise.resolve('302')
  }).then((res) => {
    console.log('401',  '上一个返回:' + res)
    return Promise.reject('401')
  }).catch((err) => {
    console.log(err)
  })
}

  • Promise.resolve() 等同用 new Promise((resolve, reject) => resolve())
  • Promise.reject() 等同用 new Promise((resolve, reject) => reject())
  • Promise.all()
// 作为参数promise 数组中,所有promise状态都是resolved才回调then第一个,只要有一个reject就reject
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then((res) => {
  console.log('通过', res)
}, (res) => {
  console.log('拒绝', res)
})

返回值为全部值的一个数组

Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.reject(3)]).then((res) => {
  console.log('通过', res)
}, (res) => {
  console.log('拒绝', res)
})

返回值仅返回拒绝的那个

  • Promise.race()
Promise.race([new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1)
  }, 1001)
}), new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2)
  }, 1002)
})]).then((res) => {
  console.log('通过', res)
}).catch((res) => {
  console.log('拒绝', res)
})

race 相当于竞赛,多个Promise竞赛,谁先状态变成resolvedrejected,谁就执行下面的回调(根据时间来抉择)

下节预告

Promise 到 generator 再到 async & await

Promise 实际应用

对异步请求封装

流程控制

手写一个Promise

后记

总结一下,从同步异步到回调,在到Promise的语法和应用全部都可以在谷歌浏览器的控制台中输出测试。通过一点点代码的编写和输出,才会让我们思维更清晰,对Promise的理解更深刻。之后再总结工作中用到的Promise,以后也会慢慢将asyncawait结合Promise来谈关于 es6 以后异步相关的新特性。