Promise的用法、原理、手写实现
1. promise 入门简介
Promise 是用来解决异步编程的问题。
1.1 JS 中分同步Api 和 异步Api。
同步API:只有当前API执行完成后,才能继续执行下一个API
for (let i = 0; i < 10000; i++) {
console.log(i);
}
console.log('同步代码执行');
只有上面一万行数值打印完,才会打印’同步代码执行’
异步API:当前API的执行不会阻塞后续代码的执行
console.log('before');
setTimeout(
() => { console.log('last');
}, 2000);
console.log('after');
setTimeout定时器要在2s秒后才执行,js引擎不会卡在定时器这,会先执行同步代码,等同步代码执行完再执行异步代码定时器(在这只需要先记住定时器是异步代码)
1.2 同步API, 异步API的区别( 获取返回值 )
同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的
同步:
// 同步
function sum (n1, n2) {
return n1 + n2;
}
const result = sum(10, 20); // result 值为 30
异步:
function getMsg () {
setTimeout(function () {
return { msg: 'Hello Node.js' }
}, 2000);
}
const msg = getMsg(); // msg 的值是 undefined
所以异步函数没法用返回值获取值
1.3 回调函数
自己定义函数让别人去调用。
使用回调函数可以获取异步API执行结果
function getMsg (callback) {
setTimeout(() => {
let a = '异步函数结果'
callback(a)
}, 2000)
}
getMsg((result) => {
console.log(result); // 异步函数结果
})
1.4代码执行顺序分析
console.log('代码开始执行');
setTimeout(() => {
console.log('2秒后执行的代码');
}, 2000);
setTimeout(() => {
console.log('0秒后执行的代码');
}, 0)
console.log('代码结束执行')
异步代码执行区的异步函数执行完成,将要执行专属的回调函数时,就会将回调函数放入回调函数队列,等同步代码执行区的代码执行完成后,就把回调函数队列的回调函数加入同步代码执行区。
1.5 JS 常见异步编程
- fs 文件操作
- 数据库操作
- AJAX 网络请求
- 定时器 (setTimeout)
1.6 Promise 出现的需求
我们先来个场景:
现在需要用 fs 文件操作 读取文件,但读取文件的顺序必须是先读A,再读B,再读C。
因为 fs 文件操作是异步的,没办法写成同步代码那样,按顺序如下
假设 fs 文件操作 是 同步的
const fs = require('fs')
fs.readFile('a.txt');
fs.readFile('b.txt');
fs.readFile('c.txt');
但 fs文件操作 是异步编程, 要按照顺序读取的话就不能写成同步代码的形式,只能这样写:
const fs = require('fs')
fs.readFile('a.txt', (err, data) => {
console.log('第一个执行', data);
fs.readFile('b.txt', (err, data) => {
console.log('第二个执行', data);
fs.readFile('c.txt', (err, data) => {
console.log(data);
})
})
})
连续嵌套着的回调函数可读性非常差,也称为回调地狱
Promise出现的目的是解决Node.js异步编程中回调地狱的问题。
先来一个Promise 案例,有个感性的认识
// resolve 解决
// reject 拒绝
// promise 的执行流程如下:
// promise 接收的参数是一个回调函数,回调函数有两个参数,resolve和 reject,在这个回调函数内部包裹一个异步操作,这个异步操作成功就调用resolve函数,失败就调用 reject
// promise 可以进行链式调用,promise.then() 是对象成功的回调, promise.catch() 是对象失败的回调
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve({name: 'aaaaa'}) // 将 promise 对象的状态设置为 成功
} else {
reject('失败了') // 将 promise 对象的状态设置为 失败
}
}, 2000);
});
promise.then(result => {
console.log(result);
}).catch( err => {
console.log(err);
})
先展示 promise 解决回调地狱的问题,后面会详细介绍promise。
Promise解决 按顺序读取A文件,B文件,C文件 的回调地狱问题:
先有个感性的认识
const fs = require('fs')
let p1 = new Promise((resolve, reject) => {
fs.readFile('a.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
let p2 = new Promise((resolve, reject) => {
fs.readFile('b.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
let p3 = new Promise((resolve, reject) => {
fs.readFile('c.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
p1.then(r1 => {
console.log(r1);
return p2;
}).then(r2 => {
console.log(r2);
return p3;
}).then(r3 => {
console.log(r3);
})
2. Promise 常见常见练习,对Promise 有更好的熟悉感
2.1 promise 实践练习-fs读取文件
// 不使用 promise
const fs = require('fs');
fs.readFile('./test.txt', (err, data) => {
// 出错,抛出错误
if (err) throw err;
console.log(data.toString());
})
// 使用 Promise 包裹
let promise = new Promise((resolve, reject) => {
fs.readFile('./test.txt', (err, data) => {
if (err) reject(err);
resolve(data.toString());
})
})
// 调用 promise 封装的异步函数
promise.then(result => {
console.log(result);
})
2.2 promise 实践练习-AJAX请求
// 原生
const btn = document.querySelector('#btn');
btn.addEventListener("click", () => {
// 创建对象
const xhr = new XMLHttpRequest();
// 初始化
xhr.open('GET', 'https://api.apiopen.top/getJoke');
// 发送
xhr.send();
// 处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 控制台输出响应体
console.log(xhr.response);
} else {
// 控制台输出状态码
console.log(xhr.status);
}
}
}
})
// promise 封装
btn.addEventListener("click", () => {
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 创建对象
const xhr = new XMLHttpRequest();
// 初始化
xhr.open('GET', 'https://api.apiopen.top/getJoke');
// 发送
xhr.send();
// 处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 判断响应状态码
if (xhr.status >= 200 && xhr.status < 300) {
// 控制台输出响应体
resolve(xhr.response);
} else {
// 控制台输出状态码
reject(xhr.status);
}
}
}
})
promise.then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
})
2.3 util.promisify 方法进行 promise 风格转化
这个是用在node.js 的环境下,我在用node.js 写后端时用到过这种方法
const util = require('util');
const fs = require('fs');
// 返回一个新的函数
// 这个函数的返回结果是promise 对象
let mineReadFile = util.promisify(fs.readFile);
mineReadFile('./test.txt').then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
2.4 promise 封装练习-AJAX请求
/**
* 封装一个函数 sendAJAX 发送 GET AJAX 请求
* 参数 URL
* 返回结果 Promise 对象
**/
function sendAJAX(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
// 处理结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status)
}
}
}
})
}
sendAJAX('https://api.apiopen.top/getJoke').then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
3. promise 的详细介绍
在了解 promise 的基本流程前,先要知道 promise的一些基本属性
3.1 promise 的状态
promise 的状态时 promise实例对象中的一个属性 [PromiseState]
- pending 进行中
- resolved / fulfilled 成功
- rejected 失败
状态只能由
Pending
变为Fulfilled
或由Pending
变为Rejected
,且状态改变之后不会在发生变化,会一直保持这个状态。
3.2. promise 对象的值
实例对象中的另一个属性 [PromiseResult]
保存着异步任务 [成功/失败] 的结果
- resolve
- reject
3.3 promise 的基本流程
3.4 promise Api 的详细介绍
3.4.1 Promise 构造函数:Promise(executor)
(1) executor 函数:执行器 (resolve, reject) => {}
(2) resolve 函数:内部定义成功时我们调用的函数 value => {}
(3) reject 函数:内部定义失败时我们调用的函数 reason => {}
说明:executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
代码
<script>
let p = new Promise((resolve, reject) => {
// 同步调用
console.log(111);
});
console.log(222);
</script>
3.4.2 Promise.prototype.then 方法 (onResolved, onRejected) => {}
(1) onResolved 函数:成功的回调函数 (value) => {}
(2) onRejected 函数:失败的回调函数 (reason) => {}
说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象
promise.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
3.4.3 Promise.catch 方法:(reason) => {}
(1) reason:失败的数据或Promise对象
说明:返回一个 失败的 promise 对象
sendAJAX('https://api.apiopen.top/getJoke').catch(reason => {
console.log(reason);
})
3.4.4 Promise.resolve 方法:(value) => {}
(1) value:成功的数据或 promise 对象
说明:返回一个成功/失败的 promise 对象
// 如果传入的参数为 非 promise类型的对象,则返回的结果为成功的promise对象
let p1 = Promise.resolve(521);
// 如果传入的参数为 promise 对象,则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('ok');
}))
console.log(p2);
3.4.5 Promise.reject 方法:(reason) => {}
(1) reason:失败的原因
说明:返回一个失败的 promise 对象
let p1 = Promise.reject(521)
let p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
3.4.6 Promise.all 方法:(promises) => {}
(1) promises:包含 n 个 promise 的数组
说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败
不演示了,写的太累了,看字面意思就知道这个函数的作用了。
3.4.7 Promise.race 方法:(promises) => {}
(1) promises:包含 n 个 promise 的数组
说明:返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态
4. promise 的几个关键问题
一:如何改变 promise 的状态
let p = new Promise((resolve, reject) => {
// 1. resolve 函数
resolve('ok') // pending ---> fulfilled
// 2. reject 函数
reject('err') // pending ---> rejected
// 3. 抛出错误
throw '出问题了';
})
二:一个 promise 指定多个成功/失败回调函数,都会调用吗?
当 promise 改变为对应状态时都会调用
let promise = new Promise((resolve, reject) => {
resolve('Ok');
})
// 指定回调函数
promise.then(res => {
console.log(res);
})
promise.then(res => {
alert(res);
})
三:改变 promise 状态 和 指定回调函数谁先谁后?
(1):都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
正常情况
promise 执行器内部是 异步操作,所以是先指定回调,再改变状态
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
// 再改变状态
resolve('Ok')
}, 1000)
})
// 先指定回调
promise.then(res => {
// 但 res 结果的获得,必须要等异步执行结束,状态改变才能获取到
console.log(res);
})
(2):如何先改状态再指定回调?
- 在执行器中直接调用 resolve() / reject()
- 延迟更长时间才调用 then()
let promise = new Promise((resolve, reject) => {
// 同步操作,直接先改变状态
resolve('Ok');
})
// 再指定回调
promise.then(res => {
console.log(res);
})
四:promise.then() 返回的新 promise 的结果状态由什么决定
(1) 简单表达:由 then() 指定的回调函数执行
(2) 详细表达:
- 如果抛出异常,新 promise 变为 rejected,reason 为 抛出的异常
- 如果返回的是非 promise 的任意值,新 promise 变为 resolved, value为返回的值
- 如果返回的是另一个新 promise,此 promise 的结果就会成为 新 promise 的结果
let promise = new Promise((resolve, reject) => {
resolve('Ok');
})
promise.then(res => {
console.log(res);
// 1. 抛出错误
// throw '出了问题'
// 2. 返回结果非 promise 对象
return 123;
// 3. 返回结果是promise 对象
return new Promise((resolve, reject) => {
resolve('DDD');
})
})
五:promise 如何串连多个操作任务?
(1) promise 的 then() 内部返回一个新的 promise,可以 .then() 进行链式调用
(2) 通过 then 的链式调用串连 多个同步/异步任务
就最开始提出按顺序读取a.txt , b.txt, c.txt 终于可以解释了。
如何用promise 解决回调地狱
const fs = require('fs')
let p1 = new Promise((resolve, reject) => {
fs.readFile('a.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
let p2 = new Promise((resolve, reject) => {
fs.readFile('b.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
let p3 = new Promise((resolve, reject) => {
fs.readFile('c.txt', 'utf-8', (err, data) => {
resolve(data)
})
})
p1.then(r1 => {
console.log(r1);
return p2;
}).then(r2 => {
console.log(r2);
return p3;
}).then(r3 => {
console.log(r3);
})
六:promise 异常穿透
(1) 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调
(2) 前面任何操作出了异常,都会传到最后失败的回调中处理
let promise = new Promise((resolve, reject) => {
reject('Err');
})
let p = promise.then(res => {
// console.log(111);
throw '失败了';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.log(reason);
})
七:中断 promise 链?
(1) 当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
(2) 办法:在回调函数中返回一个 pendding 状态的 promise 对象
let promise = new Promise((resolve, reject) => {
resolve('Ok');
})
let p = promise.then(res => {
console.log(111);
// 有且只有一个方式
// 回调函数执行的前提是 在状态改完之后才能执行。
// 这里返回的promise 状态是 pendding
return new Promise(() => {});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.log(reason);
})
5. 手写 Promise
5.1 定义整体结构
创建两个文件 index.html,promise.js
在 promise.js 写最基本的 promise 结构
function Promise(executor) {
}
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
}
index.html 里引入 我们刚写的 promise.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入 promise -->
<script src="./promise.js"></script>
</head>
<body>
<script>
let p = new Promise((resolve, reject) => {
resolve('Ok');
})
p.then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
</script>
</body>
</html>
5.2 封装 resolve 和 reject 结构
promise.js 里的 代码
function Promise(executor) {
// resolve 函数
function resolve(data) {
}
// reject 函数
function reject(data) {
}
// 同步调用 [执行器函数]
executor(resolve, reject);
}
5.3 resolve 和 reject 代码的实现
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// 保存实例对象的 this 的值
const that = this;
// resolve 函数
function resolve(data) {
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
// reject 函数
function reject(data) {
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
// 同步调用 [执行器函数]
executor(resolve, reject);
}
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
}
5.4 throw 抛出异常改变状态
try {
// 同步调用 [执行器函数]
executor(resolve, reject);
} catch (error) {
// 修改 promise 对象状态为 失败
reject(error)
}
5.5 promise 对象状态只能修改一次
// resolve 函数
function resolve(data) {
// 来个if判断一下就行
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
// reject 函数
function reject(data) {
// 判断状态
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
}
5.6 then方法执行回调
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
// 调用回调函数 PromiseState
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult);
}
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult);
}
}
5.7 异步任务回调的执行
这部分我感觉就是promise 最核心关键的地方了。
在前面的执行器函数中,一直是同步执行的,所以在 then 方法中能直接获取到 PromiseState 和 PromiseResult的值。但现在要在 executor 执行器中进行异步函数调用了,而 then 方法中回调函数就不能直接获得 PromiseState 和 PromiseResult。要等异步函数执行结束后才能得到。而如何处理才能使得 then 方法 得到 executor 执行器中 异步函数产生的值?
官方的解决方法是:
在 then 方法中判断 promise 的状态,如果是 pending,说明异步函数还没执行结束,这时不能直接调用 then 方法中的回调函数,先把回调函数保存下来。如何保存?在 promise 构造函数中用一个属性保存。
function Promise(executor) {
// 声明一个属性,用来保存 then 中的回调函数
this.callback = {}
}
然后在 pending 状态时,保存回调函数
其实这时候 then 方法已经结束了,没有把回调函数进行调用,所以就先把回调函数存到 p 这个对象的 callback 上。在异步任务结束后,交给window 托管的 resolve 开始执行,这个 window 托管的函数使用了 p 对象中存着的 callback 函数。
index.html
<script>
let p = new Promise((resolve, reject) => {
// this 的判断是根据 . 号前面的对象决定的
// 这里直接是函数,resolve内部 this 指向 window
// resolve('Ok');
setTimeout(() => {
// resolve('Ok');
reject('err')
}, 1000)
})
p.then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
console.log(p);
</script>
promise.js
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 保存回调函数
this.callback = {
onResolved: onResolved,
onRejected: onRejected
}
}
}
在 异步任务完成后,调用回调函数
// resolve 函数
function resolve(data) {
// 判断状态
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
// 调用成功的回调函数
if (that.callback.onResolved) {
that.callback.onResolved(data);
}
}
// reject 函数
function reject(data) {
// 判断状态
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
// 调用成功的回调函数
if (that.callback.onRejected) {
that.callback.onRejected(data);
}
}
5.8 指定多个回调的实现
问题:
我们希望这两个回调都能执行,但保存第二个回调时会将第一个回调覆盖掉,所以原来的保存方法不行,需要修改。
修改前
let p = new Promise((resolve, reject) => {
// this 的判断是根据 . 号前面的对象决定的
// 这里直接是函数,resolve内部 this 指向 window
// resolve('Ok');
setTimeout(() => {
// resolve('Ok');
reject('err')
}, 1000)
})
p.then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
// 保存第二个回调,会将之前保存在 p 对象中 callback 的内容覆盖掉
// 所以前面的保存方法不行,需要修改
p.then(res => {
alert(res);
}, reason => {
alert(reason);
})
修改后
改用数组存 callback,再遍历把每个 回调函数都调用。
// 声明一个属性,用来保存 then 中的回调函数
this.callback = []
// resolve 函数
function resolve(data) {
// 判断状态
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
// 遍历调用成功的回调函数
that.callback.forEach(item => {
item.onResolved(data);
});
}
// reject 函数
function reject(data) {
// 判断状态
if (that.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
that.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
that.PromiseResult = data;
// 遍历调用失败的回调函数
that.callback.forEach(item => {
item.onRejected(data);
});
}
// 添加 then 方法
Promise.prototype.then = function(onResolved, onRejected) {
// 调用回调函数 PromiseState
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult);
}
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult);
}
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 保存回调函数
this.callback.push({
onResolved: onResolved,
onRejected: onRejected
});
}
}
5.9 同步修改状态 then 方法结果返回
这一块部分相当复杂,需要慢慢来理一下,后面异步的部分会更发杂(麻了)
前面的知识前提:
我们在前面的 promise 中学到 p.then() 这个方法会返回一个 promise 对象, 返回的具体内容根 回调函数的 return 内容相关。
现在这一部分是先按同步的来,下一章内容是异步
先写整体骨架代码:
let p = new Promise((resolve, reject) => {
resolve('Ok')
})
let outerResult = p.then(value => {
return 521;
}, reason => {
console.log(reason);
})
console.log(outerResult);
我们先返回 非 promise 的值,返回 521。
在 promise.js 中 then() 方法 返回 一个 promise 对象
这是回调函数返回是 非 promise 的情况
Promise.prototype.then = function (onResolved, onRejected) {
// 返回 promise
return new Promise((resolve, reject) => {
// 调用回调函数 PromiseState
if (this.PromiseState === 'fulfilled') {
// 所以这个result就是回调函数返回的结果
let result = onResolved(this.PromiseResult);
// 如果返回的是 promise 对象
if (result instanceof Promise) {
// 这里先不处理
} else { // 返回的是常规值就是成功
// 要将 outerResult 这个 promise 目前的状态 pending 改成fulfilled
// 通过 resolve 就可以
resolve(result);
}
}
})
}
结果
tips:
我把 p.then() 返回 的 promise 对象命名为 outerResult,在 p.then() 内的回调函数返回的 promise 对象命名为 result
这是回调函数返回是 promise 的情况,并且这个 promise 是成功的状态
let outerResult = p.then(value => {
let result = new Promise((resolve, reject) => {
resolve('Hello');
})
return result;
}, reason => {
console.log(reason);
})
Promise.prototype.then = function (onResolved, onRejected) {
// 返回 promise
return new Promise((resolve, reject) => {
// 调用回调函数 PromiseState
if (this.PromiseState === 'fulfilled') {
// 获取回调函数的结果
let result = onResolved(this.PromiseResult);
// 如果返回的是 promise 对象
if (result instanceof Promise) {
// result 执行的是成功,就要给外层的 outerResult 成功的效果
result.then(value => {
// 这里 resolve 调用者是 outerResult
// result 的结果就是 outerResult的结果
resolve(value);
}, reason => {
reject(reason)
})
} else { // 返回的是常规值就是成功
// 通过 resolve 就可以
resolve(result);
}
}
})
}
完整代码
Promise.prototype.then = function (onResolved, onRejected) {
// 返回 promise
return new Promise((resolve, reject) => {
// 调用回调函数 PromiseState
if (this.PromiseState === 'fulfilled') {
try {
// 获取回调函数的结果
let result = onResolved(this.PromiseResult);
// 如果返回的是 promise 对象
if (result instanceof Promise) {
// result 执行的是成功,就要给外层的 outerResult 成功的效果
result.then(value => {
// 使 outerResult 的 状态 和 result 的状态一致
resolve(value);
}, reason => {
reject(reason)
})
} else { // 返回的是常规值就是成功
// 要将这个 大 的 promise 目前的状态 pending 改成fulfilled
// 通过 resolve 就可以
resolve(result);
}
} catch (error) {
reject(error);
}
}
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult);
}
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 保存回调函数
this.callback.push({
onResolved: onResolved,
onRejected: onRejected
});
}
})
}
5.10 异步修改状态 then 方法返回
这一部分就更复杂了。。。
因为 executor 内部是异步函数执行,所以在执行到 p.then() 时,p 的状态是 pending ,直接进入 .then() 方法的最后一个判断。说的太抽象了,来看下实际代码。
index.html
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Err');
}, 1000)
})
let outerResult = p.then(value => {
console.log(value);
// return 521;
}, reason => {
console.log(reason);
})
console.log(outerResult);
promise.js
之前写的代码,在碰到 executor 里是异步函数时,操作是先把回调函数保存起来,等异步函数执行结束,调用 resolve 或 reject 时,再调用 回调函数。
现在因为 .then() 方法执行后要返回一个 promise 对象,如果直接简单的保存回调函数,返回的 promise 对象的状态一直是 pending,PromiseResult 这个属性也一直是 null。
所以要对保存回调函数进行魔改,让后面执行这个回调函数时,能修改 outerResult 的两个属性。
Promise.prototype.then = function (onResolved, onRejected) {
// 返回 promise
return new Promise((resolve, reject) => {
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 保存回调函数
this.callback.push({
onResolved: function (data) {
// console.log('success');
// 执行成功的回调函数
// 获取回调函数返回结果
try {
let res = onResolved(data);
if (res instanceof Promise) {
// 根据 回调函数返回的 promise 决定
res.then(value => {
// 这个 回调函数 返回的 promise 内部调用的是 resolve
resolve(value);
}, reason => {
reject(reason);
})
} else {
// 不是 promise 返回的就是正确
// 改变outerResult的状态
resolve(res);
}
} catch (error) {
reject(error)
}
},
onRejected: function (data) {
try {
// 改变 promise 的状态为 rejected
let res = onRejected(data);
if (res instanceof Promise) {
// 根据 回调函数返回的 promise 决定
res.then(value => {
// 这个 回调函数 返回的 promise 内部调用的是 reject
resolve(value);
}, reason => {
reject(reason);
})
} else {
// 改变outerResult的状态
reject(res);
}
} catch (error) {
reject(error)
}
}
});
}
})
}
5.11 catch方法-异常穿透与值传递
catch 方法是获取失败的值,因为前面 then() 方法 已经写的很完善了,所以 catch 只要调用一下 then() 就好
index.html
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 1000)
})
let res = p.catch(reason => {
console.log(reason);
return 321;
})
console.log(res);
promise.js
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
}
接下来就是要完成 catch 方法的异常穿透效果
我按照顺序写下来:
index.html
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 1000)
})
let res = p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.log(reason);
})
这时候会出现这个问题:
说 onRejected 方法不存在,这是什么情况?
解释:
executor 执行器在执行它内部异步代码前,同步代码已经执行结束了,也就是 p.then() 执行完毕,把 then 内的回调方法存到了 p 的自身属性上。而 then 的回调参数有两个:onResolved,onRejected。在这个案例中只传了一个 onResolve,没有onRejected,所以 保存在 p 本身上的回调函数 onRejected 就为空。
所以我们要主动给 then 方法内加上一个 onRejected 回调函数。
Promise.prototype.then = function (onResolved, onRejected) {
// 判断回调函数的参数
// then 方法中并没有 onRejected 这个回调方法
if (typeof onRejected !== 'function') {
// 手动给 then 添加这个回调函数
onRejected = reason => {
// 抛异常
throw reason;
}
}
}
这时候在最后的 catch 就能接受到这个异常了
值传递的话就是 then 方法连 onResolved 回调函数都不传递了。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 1000)
})
let res = p.then().then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.log(reason);
})
这时候处理方法和上面的类似,加一个 onResolved 回调就行
// then 方法中并没有 onResolved 这个回调方法
if (typeof onResolved !== 'function') {
// 手动给 then 添加这个回调函数
onResolved = value => {
return value;
}
}
5.12 resolve 方法封装
比较简单直接亮代码
Promise.resolve = function (value) {
// 返回 promise 对象
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
resolve(value);
}
})
}
5.13 all 方法封装
index.html
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Ok')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
});
let p3 = new Promise((resolve, reject) => {
resolve('dddd')
});
let res = Promise.all([p1, p2, p3])
console.log(res);
promise.js
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
// 声明变量
let count = 0;
let arr = [];
// 遍历
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// 对象的状态是成功
count++;
// 将当前 promise 对象成功的结果 存到数组中
arr[i] = v;
// 判断如果全都成功,就返回成功
if (count === promises.length) {
// 修改状态
resolve(arr);
}
}, r => {
reject(r);
})
}
})
}
5.14 then 方法 回调的异步执行
先看场景:
index.html
let p1 = new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('Ok')
// }, 1000)
resolve('OK')
console.log(111);
})
p1.then(value => {
console.log(222);
})
console.log(333);
要求我们的 then 内部的回调方法应该是异步执行。
打印结果:却是同步执行
用一个比较粗糙的方法解决,给所有的回调函数加上定时器:
总结分析:
这个尚硅谷的视频一直没讲链式调用的过程,我觉得是一个很大的遗憾,我这一块还是挺迷糊的,我尝试着自己来分析一下。
首先我们需要知道 then 本身是同步,只是它内部的回调函数是异步的。可以用代码来测试一下
index.html
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Ok')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000)
});
let p3 = new Promise((resolve, reject) => {
resolve('dddd')
});
p1.then(value => {
console.log(value);
console.log(111);
return p2;
}).then(value => {
console.log(value);
console.log(222);
return p3;
}).then(value => {
console.log(value);
console.log(333);
})
console.log('我得在回调之前执行');
promise.js
修改 then 函数内容,在调用 then 时一上来就打印一下。
Promise.prototype.then = function (onResolved, onRejected) {
console.log('then本身是同步的');
}
我们可以看一下打印结果:
可以得出结论,then 的链式调用会在一开始就一下子执行下去。then 内部的回调函数是按照我们写的顺序执行下去,为啥会这样呢,我们一行一个代码逐步解析一下。
先分析一上来定义出来的三个 promise 对象
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Ok')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000)
});
let p3 = new Promise((resolve, reject) => {
resolve('dddd')
});
第一步:定义 P1 是一个 promise 对象,P1 的 PromiseState 是 pending,PromiseResult 是 null,this.callback = []。然后开始调用 executor(resolve, reject) 执行器, 但执行器内部一进去就是一个定时器,定时器是异步函数调用,进异步队列,然后继续往下执行就到 P2。
第二步:定义 P2,P2 与 P1 类似, PromiseState 和 PromiseResult 都一样,executor 执行器内部也是一个定时器,进异步队列
第三步:定义 P3,因为 P3 executor 执行器 内部没有异步函数,所以直接按照同步代码执行,而且调用的是 resolve,所以返回的是成功 PromiseState 为 fulfilled, PromiseResult 为 Ok ,callback 是 异步函数,虽然里面还没有值,还是要放到异步队列中。
开始比较刺激的 then 方法调用了。
第四步:P1 调用 then 方法,这个过程是这样的,因为我们之前在 then 的第一行添加了 console.log('then本身是同步的');
,所以控制台打印 then本身就是同步的。 接着因为 P1 的 PromiseState 是 pending ,所以会将 then 中的回调函数保存到 P1 的 callback 属性上。这时 P1.then() 方法执行结束,返回一个 Promise 对象,我把该返回的 Promise 对象命名为 X。该Promise 对象正处于 pending 状态中。
第五步:链式调用 then。
这个就相当于 X 在调用 then
X 此时 是 pending 状态,调用 then 就是 先打印下 then本身是同步的 这句话,然后就是 把 then 内部的回调函数保存 X 的 callback 上。这时, X.then() 执行结束, X.then() 返回一个对象,我称之为 Y ,这个 Y 对象状态此时也是 pending。
第六步:最后一个链式调用
p1.then(value => {
console.log(value);
console.log(111);
return p2;
}).then(value => {
console.log(value);
console.log(222);
return p3;
}).then(value => {
console.log(value);
console.log(333);
})
分解后相当于:
let X = p1.then(value => {
console.log(value);
console.log(111);
return p2;
})
let Y = X.then(value => {
console.log(value);
console.log(222);
return p3;
})
let Z = Y.then(value => {
console.log(value);
console.log(333);
})
就和上面一样,Y 此时是 pending 状态,所以调用 then 方法,直接将回调函数保存到 Y 的 callback 属性上,返回一个新 Promise ,状态是 pending。
这是同步代码执行完,控制台打印的结果:
到此为止,所有同步代码执行完毕,开始执行异步代码,这一部分又是老大难。。。
第七步:开始执行异步队列的代码
异步队列里有三个异步函数, 其实 setTimeout-回调-P3 这个异步函数并没有作用,因为 P3 的 callback 是空的。有作用的就 setTimeout-P1 和 setTimeout-P2。
因为只有 P1.then() 调用了,存有属于自己的回调函数,别的 P2,P3 都没有 .then() 拥有自己的回调函数。所以就算 P2 或 P3 的执行器里的 异步函数先完成,他们的 callback 也是空的,没有回调函数可以调用。
接下来直接讲解下这个调用过程。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Ok')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000)
});
let p3 = new Promise((resolve, reject) => {
resolve('dddd')
});
let X = p1.then(value => {
console.log(value);
console.log(111);
return p2;
})
let Y = X.then(value => {
console.log(value);
console.log(222);
return p3;
})
let Z = Y.then(value => {
console.log(value);
console.log(333);
})
console.log('X', X);
console.log('Y', Y);
console.log('Z', Z);
console.log('P1', p1);
console.log('P2', p2);
console.log('P3', p3);
还没执行前所有对象的状态
我们来看一下执行的步骤过程。
第一步:先执行完 P1 的 resolve 方法(P1 异步函数在执行时会记住 P1 的环境),将 P1 的属性都成功更改为 fulfilled 和 Ok,然后开始执行 P1 callback 里保存的回调函数。
这个回调函数是
这个回调时被修饰过的,修饰后:
执行后结果是,打印 value,打印 111,将 P2 返回给 res
P1.then() 执行结束时返回了一个 Promise 对象给 X。而 res 执行的环境是Promise X 的环境
resolve 执行就是修改 X 的状态,X 更改为 fulfilled,内容为 success ,然后要执行 X 的 callback 里的回调函数。X callback里的回调函数是 X.then() 时保存的。
然后就和上面的过程一样。
最终结果:一切都顺利执行。
本质:
尚硅谷的 promise 的实现研究后发现就是在输出结果上模拟了真实 promise 的实现过程。但实现过程不是真的 promise。如果存在这么一个场景,p2 的执行需要用到 p1 完成后的结果,那尚硅谷模拟的 promise 是无法完成的。因为 尚硅谷的 promise 只是把 p1,p2,p3的结果按顺序打印出来而已,实际上三者的异步方法在执行过程中是无关的。
6. Promisification
指将一个接受回调的函数转换为一个返回 promise 的函数。
由于许多函数和库都是基于回调的,因此,在实际开发中经常会需要进行这种转换。因为使用 promise 更加方便,所以将基于回调的函数和库 promisify 是有意义的。(译注:promisify 即指 promise 化)
具体案例可以看 2.3 2.4 的练习
7. 简介 async await
7.1 async
- 函数的返回值为 promise 对象。
- promise 对象的结果由 async 函数执行的返回值决定
7.2 await
- await 右侧的表达式一般为 promise 对象,但也可以是其他的值
- 如果表达式 promise 对象。await 返回的是 promise 成功的值
- 如果表达式是其它值,直接将此值作为await 的值
7.3 实际案例1
const fs = require('fs')
const util = require('util')
// promisify 处理完返回的是一个 promise 对象
const mineReadFile = util.promisify(fs.readFile);
async function main() {
// 读取第一个文件的内容
let data1 = await mineReadFile('./a.txt');
let data2 = await mineReadFile('./b.txt');
let data3 = await mineReadFile('./c.txt');
}
7.4 实际案例2
function sendAJAX(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
// 处理结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status)
}
}
}
})
}
let btn = document.querySelector('#btn');
btn.addEventListener('click',async function () {
// 获取信息
let message = await sendAJAX('https://api.apiopen.top/getJoke');
console.log(message);
})