1. promise 的由来
1.1 异步
我们都知道 js 是单线程的,也就是说一次只能完成一件任务。如果有多个任务,就必须排队,等待前面一个任务完成,再执行后面一个任务。
这种方式虽然实现起来比较简单,执行环境相对单纯,但是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 js 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为了解决这个问题,js 语言将任务的执行模式分成两种:同步和异步(异步任务不具有”堵塞“效应)。
“异步模式”非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是 Ajax 操作。在服务器端,”异步模式”甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有 http 请求,服务器性能会急剧下降,很快就会失去响应。
1.2 处理异步的几种方式
- 使用回调函数
1 | ajax(url, () => { |
这是几年前的方法,我们可以看出这样写简单、好实现,但是很容易写出回调地狱,如果嵌套很深,维护人员会很痛苦,此外它不能使用 try catch 捕获错误,不能直接 return。
- 事件监听
1 |
|
这种方法可以绑定多个事件,每个事件可以指定多个回调函数,而且可以”去耦合”,有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
- 发布订阅
1 | let fs = require('fs'); // fileSystem |
这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
2. promise 介绍
中文网:https://es6.ruanyifeng.com/#docs/promise
promise 本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。符合 promise A+ 规范, 按照这个规范可以自己实现一个 promise.
promise 解决的问题:
- 回调嵌套,回调地狱
- 错误捕获不好处理错误
- 多个异步同步的问题 Promise.all
promise 的几种状态
- Pending—-Promise对象实例创建时候的初始状态
- Fulfilled—-可以理解为成功的状态
- Rejected—-可以理解为失败的状态
promise 一些特性:
- 只有等待态 才能变成 成功 / 失败
- 如果状态变化后不能在修改状态
- promise中会存放两个变量, value 和 reason
promise 的实例上,会有 then 方法
1 | // 默认是等待态,可以转化成成功或者失败,状态更改后不能再更改状态 |
3. promise 链式调用
- 每次调用返回的都是一个新的 promise 实例(这就是 then 可用链式调用的原因)
- 如果 then 中返回的是一个结果的话会把这个结果传递下一次 then 中的成功回调
- 如果 then 中出现异常,会走下一个 then 的失败回调
- 在 then 中使用了return,那么 return 的值会被Promise.resolve() 包装
- then 中可以不传递参数,如果不传递会透到下一个 then 中
- catch 会捕获到没有捕获的异常
- finally 是 es9 的,不管成功或者失败都会走,不会中断运行,只是传递一个一定会执行的函数而已
1 | let Promise = require('./promise'); |
4. promise 方法
- Promise.resolve() 将现有对象转为 Promise 对象
1 | Promise.resolve('foo') |
- Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected
1 | const p = Promise.reject('出错了'); |
- Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例, 常用于无关联多接口请求处理 promise.all 执行时,若有一个reject, 边终止。
1 | const p = Promise.all([p1, p2, p3]); |
- Promise.race() 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
1 | const p = Promise.race([p1, p2, p3]); |
Promise.allsettled() es2020 新增方法,用来解决 promise.all 执行时,若有一个 reject, 边终止的情况,还是会返回执行结果
Promise.any() 只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态
5. 生成器 Generators/ yield
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。
koa 1.0 在使用, koa2 废弃调了,使用 async + await
1 | // 生成器 -> 迭代器 |
这个地方要提下什么是类数组
类数组 => 可以被迭代
也可以用 Array.prototype.slice.call(arrayLike) 来将类数组转化成数组。
1 | let arr = [...{ |
使用例子如下:
1 | function* read() { |
可以看出嵌套很深,老问题,我们常用 co 库来解决。
1 | function co(it) { |
6. 终极方法 async + await
async + await 可以理解为 generator + co 语法糖
1 | let fs = require('fs') |
所以归纳一下 js 异步编程进化史:callback -> promise -> generator -> async + await
7. 如何自己实现一个 promise
1 | function resolvePromise(promise2, x, resolve, reject) { |
上面是我简单实现的 promise
如何测试该 promise 是否合格,可参考promise test
8. 其他工具
- bbluebird 第三方库 实现 promise 化
1 | // node 中已经借鉴了,所有的异步方法参数第一个都是err |
下面自己实现一个 promisefy
1 | const promisify = (fn) => (...args) => { |
- mz 第三方包 他里面把所有的 node 模块, 都进行了包装promise
1 | let fs = require('mz/fs'); |