简介
Promise是ES6新增的内置类:承诺模式,主要用来规划异步编程的。
回调地狱
回调函数嵌套回调函数
什么是回调函数?
把一个函数作为值,传递给另外一个函数,在另外一个函数执行的时候,再把传递进来的函数进行处理。(包括调用、绑定this等)
实现一个forEach
这块为什么写forEach呢?是以为我们调用他的时候也传递了一个函数当做参数。
Function.prototype.forEach = function(callback, ctx) {
let arr = this
for(let i = 0 ; i < arr.length ; i++) {
callback && callback.call(ctx, arr[i], i, arr)
}
}
[1,2,3,4].forEach((item, index, arr)=>{
console.log(item, index, arr, this)
}, {})
回调函数的方式
// 假设我们现在有三个接口,我们还在用JQ中的ajax
// 并且这三个请求是相互依赖的,也就是串行的请求。
// 那么我们要依次执行他们
$ajax({
url: "1.txt",
success: function (res) {
$ajax({
url: "2.txt",
success: function (res) {
$ajax({
url: "3.txt",
success: function (res) {
// 最终的结果
console.log(res)
},
});
},
});
},
});
promise的方式解决回调地狱
function fn1() {
return new Promise((resolve, reject) => {
$ajax({
url: "1.txt",
success: function (res) {
// 成功的结果 调用 promise 提供的 resolve方法
resolve(res);
},
});
});
}
function fn2() {
return new Promise((resolve, reject) => {
$ajax({
url: "2.txt",
success: function (res) {
// 成功的结果 调用 promise 提供的 resolve方法
resolve(res);
},
});
});
}
function fn3() {
return new Promise((resolve, reject) => {
$ajax({
url: "3.txt",
success: function (res) {
// 成功的结果 调用 promise 提供的 resolve方法
resolve(res);
},
});
});
}
// 使用了 promise 之后就可以用 .then 的形式去拿到resolve的参数
// 既成功的结果了
fn1()
.then((res) => {
return fn2(res);
})
.then((res) => {
return fn3(res);
})
.then((res) => {
// 最终的结果
console.log(res);
});
promise + async/await
(async function(){
let result = await fn()
result = await fn(result)
result = await fn(result)
console.log(result)
})()
Promise API 探究
executor 函数
let p = new Promise([executor])
// Promise 是一个内置类
// p 是类的实例
// executor 是回调函数(必须要传递)
看以下代码的执行顺序
let p = new Promise(()=>{
console.log(1)
})
console.log(2)
1
2
- 这样我们可以看出来
executor函数是立即执行的,Promise本身是同步的(它只是用来管理异步编程的)。 - 会给
executor传递两个实参resolve函数执行会把promise实例改为成功。reject函数执行会把promise状态改成失败状态。传递的值就是promiseResult。 - 创建了一个promise实例。
Promise 实例
接下来我们再打印一下p这个实例,看看实例上面都有什么东西。
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
我们先忽略掉[[Prototype]]其实他也是__proto__
先来看其他两个
PromiseState promise状态
pending 初始状态
fulfilled 成功状态
rejected 失败状态
PromiseResult promise结果/值
初始值是 undefined
不论成功还是失败,成功的结果或者失败的原因都会赋值给这个值
PromiseState
- 状态改变成功就不能再改变了。
let p = new Promise((resolve, reject)=>{
resolve(1) // 状态改变之后就不能再改变了。
reject(100)
})
console.log(p)
p的状态还是fulfilled。
then 方法 (重点)
- 可以在
then方法调用的时候传入两个函数,并且在内部可以拿到resolve或者reject的值。 - 执行
.then方法会把成功和失败的回调函数先存起来。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
let result = Math.random();
result > 0.5 ? resolve(result) : reject(result);
}, 1000);
});
p.then(
(res) => {
console.log(`成功了${res}`);
},
(reason) => {
console.log(`失败了${reason}`);
}
);
约定一下
then的第一个参数 称为A第二个参数称为B。
- then()方法返回一个新的 Promise
p2的状态由p1then 存放的两个函数决定。p2的结果由p1then 存放的两个函数执行的返回值决定
-
不论是
A执行还是B执行,只要执行不报错,则p2的状态都是成功,反之报错就是失败(A、B函数返回的都是普通值 .then 执行结果)。 -
p1的成功和失败也会收到executor函数执行是否报错的影响,如果报错了,则p1的状态就是失败的,promiseResult的值就是失败原因,如果执行不报错,再看执行的是resolve/reject这两个中的哪一个(new Promise 执行结果)。
- new Promise 执行结果和
.then()执行出来返回的promise实例的结果是不一样的 (第6、7条)。 - 特殊情况:如果
A、B返回的都是一个新的Promsie实例,则返回的promise实例的成功失败以及结果,直接决定p2的状态和结果。
var p1 = new Promise((resolve, reject)=>{
resolve('hi')
})
var p2 = p1.then(res => {
console.log(`成功了 -> ${res}`)
return res+ '$$'
}, reason => {
console.log(`失败了 -> ${reason}`)
return reason + '$$'
})
console.log(p2)
会返回一个新的 promsie 实例
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "hi$$"
- then([A], [B]):如果第一个函数没有传递,则会“顺延”(成功顺延)
- [A]没有传递:则找下一个 then 中的 A 函数
- [B]没有传递:就找下一个 then 中的 B 函数
Promise.resolve(10).then(null, reason => {
console.log(`失败了 === ${reason}`)
})
.then(res => {
console.log(`成功了 ==== ${res}`)
return res * 10
}, reason => {
console.log(`失败了 === ${reason}`)
return reason / 10
})
- (失败顺延)
Promise.reject(10).then(null, null)
.then(res => {
console.log(`成功了 ==== ${res}`)
return res * 10
}, reason => {
console.log(`失败了 === ${reason}`)
return reason / 10
})
Promise异步流程
从下面这段代码结合Event loop进行分析一下。
let p = new Promise((resolve, reject)=>{
resolve('ok')
})
.then((res)=>{console.log(res)}, (reason)=>{
console.log(reason)
})
catch
catch相当于then(null, reason => {...})
这两个是等价的。
Promise.reject(10).then(res => {
console.log(`成功了 ${res}`)
return res * 10
}).catch(reason => {
console.log(`失败了 ${reason}`)
return reason * 10
})
Promise.reject(10).then(res => {
console.log(`成功了 ${res}`)
return res * 10
}).then(null,reason => {
console.log(`失败了 ${reason}`)
return reason * 10
})
all
管理多个promise实例,当所有实例都成功,Promise.all返回的总实例才是成功(结果是一个数组,按照顺序依次存储了每个实例执行成功的结果)
race
管理多个promise实例,看所有实例谁先有结果,无论失败还是成功,总实例的结果以最快的结果为准。
做题
then 参数全部返回正常值
new Promise((resolve, reject)=>{
resolve(10)
reject(20)
})
.then(res=>{
console.log(`成功了 --- ${res}`)
return res * 10
},reason => {
console.log(`失败了 --- ${reason}`)
return reason / 10
})
.then(res=>{
console.log(`成功了 --- ${res}`)
return res * 10
},reason => {
console.log(`失败了 --- ${reason}`)
return reason / 10
})
.then(res=>{
console.log(`成功了 --- ${res}`)
return res * 10
},reason => {
console.log(`失败了 --- ${reason}`)
return reason / 10
})
这里A B 返回的是一个普通值。如果不报错就走到下一个then的成功的回调函数里面去了。
成功了 --- 10
成功了 --- 100
成功了 --- 1000
then参数返回一个新的promise
当前promise的结果直接由这个新的promise决定。
new Promise((resolve, reject)=>{
// resolve(10)
reject(20)
})
.then(res=>{
console.log(`成功了 --- ${res}`)
return res * 10
},reason => {
console.log(`失败了 --- ${reason}`)
return reason / 10
})
.then(res=>{
console.log(`成功了 --- ${res}`)
return Promise.reject(res * 10)
},reason => {
console.log(`失败了 --- ${reason}`)
return reason / 10
})
.then(res=>{
console.log(`成功了 --- ${res}`)
return res * 10
},reason => {
console.log(`失败了 --- ${reason}`)
return reason / 10
})
失败了 --- 20
成功了 --- 2
失败了 --- 20
catch 用法
- then 只处理成功,catch处理失败(一般写在最后面)
- 返回失败状态的实例,没有做 catch 处理的会在控制台报错(但是不会影响其他的代码执行)
Promise.reject(10).then(res => {
console.log(`成功了 ${res}`)
return res * 10
}).then(res => {
console.log(`成功了 ${res}`)
return res * 10
}).catch(reason => {
console.log(`失败了 ${reason}`)
return reason * 10
})
await 后面的代码放到微任务中
async function async1() {
console.log('async1 start')
await async2()
// 执行async 函数,并且把后面的代码放到微任务中
// console.log('async1 end') 这段代码被放到了微任务中
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, (0));
async1()
new Promise((resolve, reject)=>{
console.log('promise1')
resolve()
}).then(()=>{
console.log('promise2')
})
console.log('script end')
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
async/await (重点)
async修饰一个函数:保证函数返回的是一个promise实例- 和
.then很相似、函数执行不报错,promise实例的状态就是成功的,执行报错的话,promise状态就是失败的。 - return 的值或者报错的原因就是
promise实例的结果。 - 如果
return的是一个新的promise实例,则新实例的结果影响promise的结果。 async最常见的应用,就是修饰函数。让函数中可以使用await(想要使用await,所在的函数必须使用async进行修饰)。await后面一般跟promise实例,等待promise状态改变之后在执行下面的代码。
function fn() {
return new Promise((resolve, reject)=>{
setTimeout(() => {
console.log('异步处理完成了')
resolve('ok')
}, 2000);
})
}
async function func() {
await fn()
console.log('start')
}
func()
'异步处理完成了'
'start'
await后面的promsie如果是失败的,则当前函数中的await后面的代码都不会执行了。
(async function(){
let x = await Promise.reject(10)
console.log('hhhhh') // 这里不会再执行了
})()
console.log('kkkkkkk')
- 因为以上情况,所以要使用
try catch对await进行包裹。
(async function(){
try{
let x = await Promise.reject(10)
}catch(err){
console.log(err)
}
console.log('hhhhh') // 这块还会执行
})()
console.log('kkkkkkk')
kkkkkkk
10
hhhhh
- 遇到
await先执行后面的函数,返回结果当做promise实例放在await后面。
- 不论
promise实例成功还是失败。先把await下面的代码放在的微任务队列中,继续执行同步操作。 - 当js渲染线程把同步任务都执行完成之后,并且也知道
await后面的promise是成功的状态,才会把之前存放的微任务拿来执行。
- 宏任务执行顺序是:谁先到时间谁先执行。微任务执行顺序是:谁先放到微任务队列里面谁先执行。
实现Promise深入理解
根据上面学到的东西,我们很快的就写下了以下代码。
初略版本
function MyPromise(executor) {
if (typeof executor !== "function")
throw new TypeError("参数必须是一个函数!");
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
// 1. 检验 executor 参数类型
// 2. 先调用 executor 函数
// 3. 定义 executor 函数的两个参数 resolve reject
// 4. 初始化状态和结果
// 5. then 的时候可以在成功和失败的函数中拿到 promise 实例的结果
this.promiseStatus = PENDING;
this.promiseValue = undefined;
// 成功状态改变的时候 调用的函数 相当于 then((res)=>{}, (reason)=>{}) 函数的第一个实参
this.resolveFunc = () => {};
// 失败状态改变的时候 调用的函数 相当于 then() 的第二个参数。
this.rejectFunc = () => {};
// 调用 resolve 或者 reject 函数都会改变自身实例的 结果和状态
// 所以我们写一个 专门用来改变状态的函数
var change = (status, value) => {
// 状态只能是 pending 的时候才能进来
// 意味着状态只能改变一次
if (this.promiseStatus !== PENDING) return;
this.promiseStatus = status;
this.promiseValue = value;
// 根据不同的状态调用不同的处理函数
status === FULFILLED
? this.resolveFunc(this.promiseValue)
: this.rejectFunc(this.promiseValue);
};
var resolve = (value) => {
change(FULFILLED, value);
};
var reject = (value) => {
change(REJECTED, value);
};
// executor 函数执行报错的话直接调用 reject
try {
executor(resolve, reject);
} catch (err) {
change(REJECTED, err);
}
}
MyPromise.prototype.then = function (resolveFunc, rejectFunc) {
this.resolveFunc = resolveFunc;
this.rejectFunc = rejectFunc;
};
好的我们来试一试,自己写的代码。如下,可以看见在控制台并没有输出ok, 现在我们来想一想为什么没有输出呢?我们很快的发现在调用executor函数的时候,我们.then()函数还没有调用,当然里面传递的成功的回调和失败的回调都还没有注册到this.resolveFunc和this.rejectFunc上面去。所以在 调用resolve()的时候不会触发.then()里面注册的成功的回调函数。下面我们想办法解决他。
let p = new MyPromise((resolve, reject)=>{
resolve('ok')
});
p.then(
(res) => {
console.log(res);
},
(reason) => {
console.log(reason);
}
);
我们的解决方案是让在change函数里面把调用注册then成功和失败的方法异步执行。
支持.then中进行回调
var change = (status, value) => {
// 状态只能是 pending 的时候才能进来
// 意味着状态只能改变一次
if (this.promiseStatus !== PENDING) return;
this.promiseStatus = status;
this.promiseValue = value;
// 根据不同的状态调用不同的处理函数
// 由于在执行 executor 函数的时候 内部调用了 resolve更改status和value是同步的
// 但是执行 通知方法要的是异步的(因为此时.then()后面的方法还没执行回调函数还没注册好)
// 所以我们用srtTimeout() 把执行 then里面的方法异步执行
setTimeout(() => {
status === FULFILLED
? this.resolveFunc(this.promiseValue)
: this.rejectFunc(this.promiseValue);
}, 0);
};
两个静态方法
下面再扩展两个静态方法es5中就是属性是一个函数
MyPromise.resolve = function (result) {
return new MyPromise((resolve) => {
resolve(result);
});
};
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
};
then 成功失败顺延
MyPromise.prototype.then = function (resolveFunc, rejectFunc) {
// 不传递参数的话可以顺延
if (typeof resolveFunc !== "function") {
// 默认返回一个成功的 promise
resolveFunc = (result) => {
return new MyPromise.resolve(result);
};
}
if(typeof rejectFunc !== 'function') {
rejectFunc = (reason) => {
// 默认返回一个失败的 promise
return new MyPromise.reject(reason)
}
}
this.resolveFunc = resolveFunc;
this.rejectFunc = rejectFunc;
};
then的链式调用 (难点)
分析一下。
- 每次执行then方法都会返回一个新的
promise实例 - 构造新
promise实例executor函数的resolve和reject方法控制返回实例的状态和结果。 - 需要在新的
promise实例的resolve或者reject调用的时候拿到上一个promise返回的结果。 下面我们就来写一下,主要更改then后面的逻辑
// this.resolveFunc = resolveFunc;
// this.rejectFunc = rejectFunc;
return new MyPromise((resolve, reject)=>{
// 把之前的函数包一层,在内部进行调用,
// 1. 拿到 上一个 then的返回结果
// 2. 检测上一个 then的回调函数是否报错
this.resolveFunc = (result) =>{
try{
// 如果执行不报错,则返回的实例是成功的(特殊情况:then A 方法也返回的是一个promise)
// 则由返回的promise实例决定这个实例的状态
let x = resolveFunc(result)
x instanceof MyPromise ?
x.then(resolve, reject) : resolve(x)
}catch(err){
// 如果执行报错则这个实例的状态就失败
reject(err)
}
}
this.rejectFunc = (reason) =>{
try{
let x = rejectFunc(reason)
x instanceof MyPromise ?
x.then(resolve, reject) : resolve(x)
}catch(err){
reject(err)
}
}
promise.catch
相当于then不传第一个参数
MyPromise.prototype.catch = function (rejectFunc) {
return this.then(null, rejectFunc)
}
promise.all
要点:
- 对数组中的 item 进行类型检测,如果是promsie的实例,就执行
then取结果,如果不是就直接把item放在结果对应的位置上面。 - 进行计数,并且每次放完结果的时候
index++,表示已经处理完数组中的一个item了,当index >= 传入数组的长度的时候就代表整个promise数组执行完成了,就可以调用resolve把整个结果fire(抛出去了)。当发现有一个失败的就直接reject掉。
MyPromise.all = function (promiseArr) {
return new MyPromise((resolve, reject) => {
let index = 0,
results = [];
for (let i = 0; i < promiseArr.length; i++) {
// 如果index >= 数组的长度 就抛出最终的结果
let fire = () => {
if (index >= promiseArr.length) {
resolve(results);
return;
}
};
// 先看每个item是不是一个 promise ,如果不是的话就把他放在对应的结果上面
let item = promiseArr[i];
// 不是promise 就直接设置返回结果
if (!(item instanceof MyPromise)) {
results[i] = item;
index++;
fire()
continue;
}
// 如果是promise 就执行
item
.then((res) => {
results[i] = res;
index++;
fire()
})
.catch((reason) => {
reject(reason);
});
}
});
}