一、js中的同步api与异步api
1.同步api:
只有当前api执行完成之后才会继续向下执行
同步执行的api可以获取到前面执行的api的返回结果
2.异步api:
异步api可以同时执行,不会互相阻塞
异步api获取不到代码顺序上前面的api的返回结果
举个栗子:
// 同步
function fn(num1, num2) {
return num1 + num2;
}
let num = fn(1, 2) // num的值为3
function fn2() {
setTimeout(function () {
return 'hellow js'
}, 1000)
}
let str = fn2() // 值为undefined
但是通过回调函数可以取得异步执行的结果
function fn(cb) {
setTimeout(function () {
let str = '异步回调'
cb(str)
}, 1000)
}
fn((str) => {
console.log(str) // 异步回调
})
异步代码执行区的异步api执行完成,将要执行专属的回调函数时,就会将回调函数放入回调函数队列,等同步代码执行区的代码执行完成后,就把回调函数队列的回调函数加入同步代码执行区。
定时器 ajax请求,都是异步执行
二、promise出现的原因及需求
1.我们在执行api时,通常会用到上一个api的执行结果
const btn = document.getElementById('btn')
btn.addEventListener('click', () => {
// 创建一个request
const request = new XMLHttpRequest();
// 初始化请求
request.open('get', 'https://xxx/xxx/xxx:xxxx')
// 发送请求
request.send();
// 处理返回参数
request.onreadystatechange = function () {
// 判断请求状态码
if (request.readyState === 4) {
if (request.status === 200) {
// 输出返回参数
console.log(request.response);
// 在没有使用promise时,如果我们想要使用此函数的返回结果
// 就需要在这里进行回调
request.open('get', `https://xxx/xxx/xxx:xxxx?xx=${request.response}`)
// 发送请求
request.send();
request.onreadystatechange = function () {
}
}
}
}
})
此时,如果有a,b,c...等若干个函数,切,下一个函数总是依赖上一个函数的执行结果,就会一直嵌套调用,也就是我们所说的回调地狱。
如果我们使用promise封装一个ajax请求
function requestAjax(url) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', url);
request.send();
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status = 200) {
resolve(request.response);
} else {
reject(request.status)
}
}
}
})
}
requestAjax('xxx/xxx/xxxx:xxxx').then(() => {
// 在这里进行下一步执行的函数处理
}).catch(() => {
})
当我们使用promise去封装一个ajax请求后,这样可以避免回调地狱,让我们代码的可读性更高。
三、promise的状态、属性、基本流程
1.状态
promise的状态是promise对象中的一个属性
- pending 进行中
- resolved / fulfilled 成功
- rejected 失败
状态只能由 Pending
变为 Fulfilled
或由 Pending
变为 Rejected
,且状态改变之后不会在发生变化,会一直保持这个状态
promise中另一个属性保存的是异步任务的执行结果 [[PromiseResult]]
2.流程
四、手写一个promise
1.定义一个Promise框架
// 定义一个promise函数并添加一个then方法
function Promise(executor) {
}
// 给promise添加 then 方法
Promise.prototype.then = function (onResolved, onRejected) {
}
2.封装resolve和reject
// 封装resolve和reject
function Promise(executor) {
// resolve 函数
resolve = (data) => {}
// reject 函数
reject = (data) => {}
// 同步调用 [执行器函数]
executor(resolve, reject);
}
3.resolve和reject的实现及添加状态和属性
// resolve和reject的实现
function Promise(executor) {
// 添加属性用来保存状态
this.PromiseState = 'pending';
// 添加属性用来保存结果
this.PromiseResult = null;
// resolve 函数
resolve = (data) => {
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
}
// reject 函数
reject = (data) => {
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
}
// 同步调用 [执行器函数]
executor(resolve, reject);
}
4.抛出异常,改变状态
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// resolve 函数
resolve = (data) => {
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
}
// reject 函数
reject = (data) => {
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
}
// 抛出异常 并改变当前状态
try {
// 同步调用 [执行器函数]
executor(resolve, reject);
} catch (error) {
// 修改 promise 对象状态为 失败
reject(error)
}
}
5.promise的状态只允许修改一次,这里加一个判断就好,看下当前的状态
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// resolve 函数
resolve = (data) => {
if (this.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
}
// reject 函数
reject = (data) => {
if (this.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
}
// 抛出异常 并改变当前状态
try {
// 同步调用 [执行器函数]
executor(resolve, reject);
} catch (error) {
// 修改 promise 对象状态为 失败
reject(error)
}
}
6.then方法执行回调
Promise.prototype.then = function (onResolved, onRejected) {
// 判断当前执行的状态
if (this.PromiseState === 'fulfilled') {
onResolved(this.PromiseResult);
}
if (this.PromiseState === 'rejected') {
onRejected(this.PromiseResult);
}
}
7.异步任务回调的执行
前面我们说的都是在函数执行器同步执行的情况下,所以在then方法中我们可以获取到PromiseState和PromiseResult的值,但是如果要是在executor函数执行器中进行异步调用then方法是不能直接获取到PromiseState和PromiseResult的值的,但是then方法的执行又需要依赖于PromiseState与PromiseResult的值,该如何处理。
官方的解决方案是,在then方法中先判断一下当前的状态,如果是pending,证明异步函数还没有执行完成,这时不可以直接调用then中的回调函数,可以先把回调函数作为promise中的一个属性保存起来。
function Promise(executor) {
// 声明一个属性,用来保存 then 中的回调函数
this.callback = {}
}
在pending时将回调保存
其实这时候 then 方法已经结束了,没有把回调函数进行调用,所以就先把回调函数存到 p 这个对象的 callback 上。在异步任务结束后,交给window 托管的 resolve 开始执行,这个 window 托管的函数使用了 p 对象中存着的 callback 函数。
我们新建一个html来调用promise
<!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>
</head>
<body>
<div id="btn">
</div>
</body>
<script src="./promise.js"></script>
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err')
}, 1000)
})
p.then(res => {
console.log(res);
}, reason => {
console.log(reason);
})
console.log(p);
</script>
</html>
在then中保存回调
Promise.prototype.then = function(onResolved, onRejected) {
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 保存回调函数
this.callback = {
onResolved: onResolved,
onRejected: onRejected
}
}
}
异步函数执行完成之后,调用回调函数
// resolve 函数
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// resolve 函数
resolve = (data) => {
if (this.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
// 调用onResolved
if (this.callback.onResolved) {
this.callback.onResolved(data);
}
}
// reject 函数
reject = (data) => {
if (this.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
// 调用onRejected
if (this.callback.onRejected) {
this.callback.onRejected(data);
}
}
// 抛出异常 并改变当前状态
try {
// 同步调用 [执行器函数]
executor(resolve, reject);
} catch (error) {
// 修改 promise 对象状态为 失败
reject(error)
}
}
8.执行多个回调函数
我们希望上述的两个回调函数都可以执行,但是当我们向callback属性存储回调函数时,后面的回调就会将前面的回调给覆盖,这时,我们可以采用数组存储回调函数,执行时再将数组中的方法进行遍历。
// 1. 定义一个promise函数
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// resolve 函数
resolve = (data) => {
if (this.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'fulfilled';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
// 调用onResolved
this.callback.forEach(item => {
item.onResolved(data);
});
}
// reject 函数
reject = (data) => {
if (this.PromiseState !== 'pending') return;
// 1. 修改对象的状态 (promiseState)
this.PromiseState = 'rejected';
// 2. 设置对象结果值 (promiseResult)
this.PromiseResult = data;
// 调用onRejected
this.callback.forEach(item => {
item.onRejected(data);
});
}
// 抛出异常 并改变当前状态
try {
// 同步调用 [执行器函数]
executor(resolve, reject);
} catch (error) {
// 修改 promise 对象状态为 失败
reject(error)
}
}
// 2. 给promise添加 then 方法
Promise.prototype.then = function (onResolved, onRejected) {
console.log(this)
// 调用回调函数 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
});
}
}
9.then方法返回的结果,同步修改
我们在上面说到,then方法会返回一个新的promise对象,返回的具体内容与回调函数的return有关
这里先进行一个返回值为非promise对象的情况
在调用promise时
let p = new Promise((resolve, reject) => {
resolve('Ok')
})
let str = p.then(value => {
return '111'
})
console.log(str);
而在then方法中,我们需要返回一个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);
}
}
})
}
控制台看一下结果
这里,outerResult为p.then() 返回 的 promise 对象,在 p.then() 内的回调函数返回的 promise 对象为 result
再看一下回调函数是promise的情况
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
});
}
})
}
调用
let p = new Promise((resolve, reject) => {
resolve('111');
})
let outerResult = p.then(value => {
let result = new Promise((resolve, reject) => {
resolve('222');
})
return result;
})
console.log(outerResult)
看下结果
10.异步修改then返回
前面写的代码,在碰到executor 里是异步函数时,就是将回调函数进行保存,等异步函数执行完,再调用回调函数。但是,如果then方法返回的是一个promise对象,如果直接将回调函数保存再调用的话,返回的promise对象将会一直处于pending状态。
上代码
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)
}
}
});
}
})
}
11.catch方法
前面的then方法已经比较完善了,所以我们在这里直接调用一下then方法
Promise.prototype.catch = function (onRejected) { return this.then(undefined, onRejected); }
在promise中,then的链式调用,可以指定失败的回调,前面任何操作出现异常,都会传到最后失败处理的回调函数中
executor 执行器在执行它内部异步代码前,同步代码已经执行结束了,也就是 p.then() 执行完毕,把 then 内的回调方法存到了 p 的自身属性上。而 then 的回调参数有两个:onResolved,onRejected。如果我们只传一个参数就会这样
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);
})
这是因为,在这个栗子中只传了一个 onResolve,没有onRejected,所以 保存在 p 本身上的回调函数 onRejected 就为空。 所以,我们需要在then方法中加入一个onRejected回调函数
Promise.prototype.then = function (onResolved, onRejected) {
// 返回 promise
return new Promise((resolve, reject) => {
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 判断回调函数的参数
// then 方法中并没有 onRejected 这个回调方法
if (typeof onRejected !== 'function') {
// 手动给 then 添加这个回调函数
onRejected = reason => {
// 抛异常
throw reason;
}
}
// 保存回调函数
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)
}
}
});
}
})
}
然后catch就可以接收这个异常了
值传递的话就是在then方法中加一个 onResolved 回调就行了
Promise.prototype.then = function (onResolved, onRejected) {
// 返回 promise
return new Promise((resolve, reject) => {
// 判断 pending 状态
if (this.PromiseState === 'pending') {
// 判断回调函数的参数
// then 方法中并没有 onRejected 这个回调方法
if (typeof onRejected !== 'function') {
// 手动给 then 添加这个回调函数
onRejected = reason => {
// 抛异常
throw reason;
}
}
// then 方法中并没有 onResolved 这个回调方法
if (typeof onResolved !== 'function') {
// 手动给 then 添加这个回调函数
onResolved = value => {
return value;
}
}
// 保存回调函数
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)
}
}
});
}
})
}
12.promise.all方法的封装
比较简单,直接上代码
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);
})
}
})
}
示例
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('111')
}, 500)
})
let p2 = new Promise((resolve, reject) => {
resolve('222')
});
let p3 = new Promise((resolve, reject) => {
resolve('333')
});
let res = Promise.all([p1, p2, p3])
console.log(res);
五、这样,一个完整的promise基本已经成型了。