十、Promise详解
异步任务的处理
-
在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise
- 虽然等你学会Promise之后,会觉得Promise不过如此,但是在初次接触的时候都会觉得这个东西不好理解;
-
那么这里我从一个实际的例子来作为切入点:
- 我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟);
- 如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去;
- 如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息;
/**
* 这种回调的方式有很多的弊端:
* 1> 如果是我们自己封装的requestData,那么我们在封装的时候必须要自己设计好callback名称, 并且使用好
* 2> 如果我们使用的是别人封装的requestData或者一些第三方库, 那么我们必须去看别人的源码或者文档, 才知道它这个函数需要怎么去获取到结果
*/
// request.js
function requestData(url, successCallback, failtureCallback) {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
// url传入的是coderwhy, 请求成功
if (url === "coderwhy") {
// 成功
let names = ["abc", "cba", "nba"]
successCallback(names)
} else { // 否则请求失败
// 失败
let errMessage = "请求失败, url错误"
failtureCallback(errMessage)
}
}, 3000);
}
// main.js
requestData("kobe", (res) => {
console.log(res)
}, (err) => {
console.log(err)
})
// 更规范/更好的方案 Promise承诺(规范好了所有的代码编写逻辑)
function requestData2() {
return "承诺"
}
const chengnuo = requestData2()
Promise简介
什么是Promise呢?
-
在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
- 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
- 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用;
-
我们来看一下Promise的API是怎么样的:
-
Promise是一个类,可以翻译成 承诺、许诺 、期约;
-
当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
-
在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
- 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
- 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
-
Promise代码结构:
// 传入的这个函数, 被称之为 executor
// > resolve: 回调函数, 在成功时, 回调resolve函数
// >reject: 回调函数, 在失败时, 回调reject函数
const promise = new Promise((resolve, reject) => {
// console.log("promise传入的函数被执行了")
// resolve()
reject()
})
promise.then(() => {
})
promise.catch(() => {
})
上面Promise使用过程,我们可以将它划分成三个状态:
-
待定(pending) : 初始状态,既没有被兑现,也没有被拒绝;
- 当执行executor中的代码时,处于该状态;
-
已兑现(fulfilled) : 意味着操作成功完成;
- 执行了resolve时,处于该状态;
-
已拒绝(rejected) : 意味着操作失败;
- 执行了reject时,处于该状态;
用了Promise,我们就可以将之前的代码进行重构了:
// request.js
function requestData(url) {
// 异步请求的代码会被放入到executor中
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
// url传入的是coderwhy, 请求成功
if (url === "coderwhy") {
// 成功
let names = ["abc", "cba", "nba"];
resolve(names);
} else {
// 否则请求失败
// 失败
let errMessage = "请求失败, url错误";
reject(errMessage);
}
}, 3000);
});
}
// main.js
const promise = requestData("coderwhy");
promise.then(
(res) => {
console.log("请求成功:", res);
},
(err) => {
console.log("请求失败:", err);
}
);
Executor
-
Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:
-
通常我们会在Executor中确定我们的Promise状态:
- 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved);
- 通过reject,可以拒绝(reject)Promise的状态;
-
这里需要注意:一旦状态被确定下来,Promise的状态会被锁死,该Promise的状态是不可更改的
- 在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成兑现(fulfilled);
- 在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);
resolve不同值的区别
- 情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
- 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态:
- 情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态:
// 完全等价于下面的代码
// 注意: Promise状态一旦确定下来, 那么就是不可更改的(锁定)
new Promise((resolve, reject) => {
// pending状态: 待定/悬而未决的
console.log("--------");
reject(); // 处于rejected状态(已拒绝状态)
resolve(); // 处于fulfilled状态(已敲定/兑现状态)
console.log("++++++++++++");
}).then(
(res) => {
console.log("res:", res);
},
(err) => {
console.log("err:", err);
}
);
常见方法
then方法
-
then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的 Promise.prototype.then
-
then方法接受两个参数:
- fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
- reject的回调函数:当状态变成reject时会回调的函数;
promise.then(res => {
console.log("res:", res);
}, err => {
console.log("err:", err);
})
// 等价于
promise.then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err:", err);
})
多次调用
-
一个Promise的then方法是可以被多次调用的:
- 每次调用我们都可以传入对应的fulfilled回调;
- 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
promise.then(res => {
console.log("res1:", res)
})
promise.then(res => {
console.log("res2:", res)
})
promise.then(res => {
console.log("res3:", res)
})
返回值
-
Promise有三种状态,那么这个Promise处于什么状态呢?
-
当then方法中的回调函数本身在执行的时候,那么它处于pending状态;
-
当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;
- 情况一:返回一个普通的值;
- 情况二:返回一个Promise;
- 情况三:返回一个thenable值;
-
当then方法抛出一个异常时,那么它处于reject状态;
-
// 2.then方法传入的 "回调函数: 可以有返回值
// then方法本身也是有返回值的, 它的返回值是Promise
// 1> 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined), 那么这个普通的值被作为一个新的Promise的resolve值
promise
.then((res) => {
return "aaaaaa";
})
.then((res) => {
console.log("res:", res);
return "bbbbbb";
});
// 2> 如果我们返回的是一个Promise
promise.then(res => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111111)
}, 3000)
})
}).then(res => {
console.log("res:", res)
})
// 3> 如果返回的是一个对象, 并且该对象实现了thenable
promise
.then((res) => {
return {
then: function (resolve, reject) {
resolve(222222);
},
};
})
.then((res) => {
console.log("res:", res);
});
catch方法
-
catch方法也是Promise对象上的一个方法:它也是放在Promise的原型上的 Promise.prototype.catch
-
一个Promise的catch方法是可以被多次调用的:
- 每次调用我们都可以传入对应的reject回调;
- 当Promise的状态变成reject的时候,这些回调函数都会被执行
promise.catch(err => {
console.log("err:", err);
})
返回值
事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:
- 但是第一个promise会返回一个undefined
- 如果我们希望后续继续执行catch,那么需要抛出一个异常:
promise
.catch(err => {
console.log("err1:", err);
// throw new Error("error message")
})
.catch(err => {
console.log("err2:", err);
})
.then(res => {
console.log("res:", res);
})
// err1: hahaha
// res: undefined
finally方法
- finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。
- finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。
promise
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err);
})
.finally(() => {
console.log("finally code execute");
});
resolve方法
- 有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
- Promise.resolve的用法相当于new Promise,并且执行resolve操作:
// 类方法Promise.resolve
// 1.普通的值
const promise = Promise.resolve({ name: "why" })
// 相当于
const promise2 = new Promise((resolve, reject) => {
resolve({ name: "why" })
})
// 2.传入Promise
const promise = Promise.resolve(new Promise((resolve, reject) => {
resolve("11111")
}))
promise.then(res => {
console.log("res:", res)
})
// 3.传入thenable对象
const promise = Promise.resolve((res) => {
return {
then: function (resolve, reject) {
resolve(222222);
},
};
})
-
resolve参数的形态:
- 情况一:参数是一个普通的值或者对象
- 情况二:参数本身是Promise
- 情况三:参数是一个thenable
reject方法
- reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
- Promise.reject的用法相当于new Promise,只是会调用reject:
- Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
// 注意: 无论传入什么值都是一样的
const promise = Promise.reject(new Promise(() => {}));
promise
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err);
});
all方法
另外一个类方法是Promise.all:
-
它的作用是将多个Promise包裹在一起形成一个新的Promise;
-
新的Promise状态由包裹的所有Promise共同决定:
- 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
- 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
// 创建多个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(11111);
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(22222);
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(33333);
}, 3000);
});
// 需求: 所有的Promise都变成fulfilled时, 再拿到结果
// 意外: 在拿到所有结果之前, 有一个promise变成了rejected, 那么整个promise是rejected
Promise.all([p2, p1, p3, "aaaa"])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log("err:", err);
});
allSettled方法
- 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
- 并且这个Promise的结果一定是fulfilled的;
// allSettled
Promise.allSettled([p1, p2, p3])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
/*
[
{ status: 'fulfilled', value: 11111 },
{ status: 'rejected', reason: 22222 },
{ status: 'fulfilled', value: 33333 }
]
*/
-
我们来看一下打印的结果:
- allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
- 这个对象中包含status状态,以及对应的value值;
race方法
-
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:
- race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
// race: 竞技/竞赛
// 只要有一个Promise变成fulfilled状态, 那么就结束
// 意外:
Promise.race([p1, p2, p3])
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err);
});
any方法
-
any方法是ES12中新增的方法,和race方法是类似的:
- any方法会等到一个fulfilled状态,才会决定新Promise的状态;
- 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
Promise.any([p1, p2, p3])
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err.errors);
});
/*
[
1111,
22222,
3333
]
*/
- 如果所有的Promise都是reject的,那么会报一个AggregateError的错误
手写Promise
手写基础框架
// 设定promise的状态码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class HYPromise {
// 立即执行函数constructor初始化promise
constructor(executor) {
// Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行 通常我们会在Executor中确定我们的Promise状态
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
// resolve函数
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 更改状态
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
console.log("resolve被调用");
}
};
// reject函数
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
console.log("reject被调用");
}
};
// 返回executor
executor(resolve, reject);
}
}
const promise = new HYPromise((resolve, reject) => {
console.log("状态pending");
resolve(1111);
reject(2222);
});
手写then方法(初始版)
- then方法会有两个函数,一个成功回调,一个失败回调
- 在调用then方法后会延时调用成功或者失败回调(因为此时在执行constructor初始化还没有调用then方法)
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class HYPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED;
// queueMicrotask 延时调用 不然会this.onFulfilled is not a function
queueMicrotask(() => {
this.value = value;
this.onFulfilled(this.value);
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
console.log("执行onRejected");
this.reason = reason;
this.onRejected(this.reason);
});
}
};
executor(resolve, reject);
}
// then方法传入两个回调函数
then(onFulfilled, onRejected) {
console.log("执行then方法");
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
}
const promise = new HYPromise((resolve, reject) => {
console.log("状态pending");
reject(2222)
resolve(1111);
});
// 调用then方法
promise.then(
(res) => {
console.log("res1:", res);
return 1111;
},
(err) => {
console.log("err:", err);
}
);
/*
状态pending
执行then方法
执行onRejected
err: 2222
*/
手写then方法(优化一)
- 优化调用then方法不能多次调用
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
class HYPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
// 新增两个Fns来存放多次调用的promise的回调函数
this.onFulfilledFns = []
this.onRejectedFns = []
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
// 再次检测promise状态
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED
this.value = value
// 轮流执行promise的回调
this.onFulfilledFns.forEach(fn => {
fn(this.value)
})
});
}
}
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
this.onRejectedFns.forEach(fn => {
fn(this.reason)
})
})
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
// 1.如果在then调用的时候, 状态已经确定下来
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
onFulfilled(this.value)
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
onRejected(this.reason)
}
// 2.将成功回调和失败的回调放到数组中
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(onFulfilled)
this.onRejectedFns.push(onRejected)
}
}
}
const promise = new HYPromise((resolve, reject) => {
console.log("状态pending")
resolve(1111) // resolved/fulfilled
reject(2222)
})
// 调用then方法多次调用
promise.then(res => {
console.log("res1:", res)
}, err => {
console.log("err:", err)
})
promise.then(res => {
console.log("res2:", res)
}, err => {
console.log("err2:", err)
})
// 在确定Promise状态之后, 再次调用then
setTimeout(() => {
promise.then(res => {
console.log("res3:", res)
}, err => {
console.log("err3:", err)
})
}, 1000)
/*
状态pending
res1: 1111
res2: 1111
res3: 1111
*/
手写then方法(优化二)
- 优化then方法不能链式调用
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
// 工具函数 检测execFn能否正常执行,如果不行就抛出异常交给reject执行
function execFunctionWithCatchError(execFn, value, resolve, reject) {
try {
const result = execFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class HYPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 直接将一个新new出来的promise返回出去
return new HYPromise((resolve, reject) => {
// 1.如果在then调用的时候, 状态已经确定下来
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
// try {
// const value = onFulfilled(this.value)
// resolve(value)
// } catch(err) {
// reject(err)
// }
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// try {
// const reason = onRejected(this.reason)
// resolve(reason)
// } catch(err) {
// reject(err)
// }
execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
}
// 2.将成功回调和失败的回调放到数组中
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(() => {
// try {
// const value = onFulfilled(this.value)
// resolve(value)
// } catch(err) {
// reject(err)
// }
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
});
this.onRejectedFns.push(() => {
// try {
// const reason = onRejected(this.reason)
// resolve(reason)
// } catch(err) {
// reject(err)
// }
execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
});
}
});
}
}
const promise = new HYPromise((resolve, reject) => {
console.log("状态pending");
// resolve(1111) // resolved/fulfilled
reject(2222);
// throw new Error("executor error message")
});
// 调用then方法多次调用
promise
.then(
(res) => {
console.log("res1:", res);
return "aaaa";
// 当抛出异常时会交给下一个promise的reject处理
// throw new Error("err message")
},
(err) => {
console.log("err1:", err);
return "bbbbb";
// throw new Error("err message")
}
)
.then(
(res) => {
console.log("res2:", res);
},
(err) => {
console.log("err2:", err);
}
);
手写catch方法
- catch方法直接调用this.then(undefined, onRejected);
- then方法检测一下onRejected是否为undefined,如果是undefined直接抛出异常交给下一个promise的reject处理
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
try {
const result = execFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class HYPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 当调用onRejected为underfined时,直接抛出异常交给下一个promise处理
const defaultOnRejected = (err) => {
throw err;
};
onRejected = onRejected || defaultOnRejected;
return new HYPromise((resolve, reject) => {
// 1.如果在then调用的时候, 状态已经确定下来
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
}
// 2.将成功回调和失败的回调放到数组中
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled)
this.onFulfilledFns.push(() => {
execFunctionWithCatchError(
onFulfilled,
this.value,
resolve,
reject
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject
);
});
}
});
}
catch(onRejected) {
this.then(undefined, onRejected);
}
}
const promise = new HYPromise((resolve, reject) => {
console.log("状态pending");
// resolve(1111) // resolved/fulfilled
reject(2222);
});
// 调用then方法多次调用
promise
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err);
});
手写finally方法
- 再给onFulfilled也检测一下是否为underfined,如果为underfined则交给下一个promise的resolve处理
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
try {
const result = execFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class HYPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
const defaultOnRejected = (err) => {
throw err;
};
onRejected = onRejected || defaultOnRejected;
// 在失败回调时将OnFulfilled交给下一个promise来处理
const defaultOnFulfilled = (value) => {
return value;
};
onFulfilled = onFulfilled || defaultOnFulfilled;
return new HYPromise((resolve, reject) => {
// 1.如果在then调用的时候, 状态已经确定下来
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
}
// 2.将成功回调和失败的回调放到数组中
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled)
this.onFulfilledFns.push(() => {
execFunctionWithCatchError(
onFulfilled,
this.value,
resolve,
reject
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject
);
});
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(onFinally) {
this.then(
() => {
onFinally();
},
() => {
onFinally();
}
);
}
}
const promise = new HYPromise((resolve, reject) => {
console.log("状态pending");
resolve(1111); // resolved/fulfilled
// reject(2222)
});
// 调用then方法多次调用
promise
.then((res) => {
console.log("res1:", res);
return "aaaaa";
})
.then((res) => {
console.log("res2:", res);
})
.catch((err) => {
console.log("err:", err);
})
.finally(() => {
console.log("finally");
});
手写resolve和reject方法
static resolve(value) {
return new HYPromise((resolve) => resolve(value));
}
static reject(reason) {
return new HYPromise((resolve, reject) => reject(reason));
}
HYPromise.resolve("Hello World").then((res) => {
console.log("res:", res);
});
HYPromise.reject("Error Message").catch((err) => {
console.log("err:", err);
});
手写all和allSettled方法
static all(promises) {
// 问题关键: 什么时候要执行resolve, 什么时候要执行reject
return new HYPromise((resolve, reject) => {
const values = [];
promises.forEach((promise) => {
promise.then(
(res) => {
values.push(res);
if (values.length === promises.length) {
resolve(values);
}
},
(err) => {
reject(err);
}
);
});
});
}
static allSettled(promises) {
return new HYPromise((resolve) => {
const results = [];
promises.forEach((promise) => {
promise.then(
(res) => {
results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
if (results.length === promises.length) {
resolve(results);
}
},
(err) => {
results.push({ status: PROMISE_STATUS_REJECTED, value: err });
if (results.length === promises.length) {
resolve(results);
}
}
);
});
});
}
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(1111);
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2222);
}, 2000);
});
const p3 = new Promise((resolve) => {
setTimeout(() => {
resolve(3333);
}, 3000);
});
HYPromise.all([p1, p2, p3]).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
HYPromise.allSettled([p1, p2, p3]).then((res) => {
console.log(res);
});
手写raceh和any方法
static race(promises) {
return new HYPromise((resolve, reject) => {
promises.forEach((promise) => {
// promise.then(res => {
// resolve(res)
// }, err => {
// reject(err)
// })
promise.then(resolve, reject);
});
});
}
static any(promises) {
// resolve必须等到有一个成功的结果
// reject所有的都失败才执行reject
const reasons = [];
return new HYPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve, (err) => {
reasons.push(err);
if (reasons.length === promises.length) {
reject(new AggregateError(reasons));
}
});
});
});
}
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1111);
}, 3000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2222);
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(3333);
}, 3000);
});
HYPromise.race([p1, p2, p3]).then(res => {
console.log("res:", res)
}).catch(err => {
console.log("err:", err)
})
HYPromise.any([p1, p2, p3])
.then((res) => {
console.log("res:", res);
})
.catch((err) => {
console.log("err:", err.errors);
})
总结
一. Promise规范
二. Promise类设计
class HYPromise {}
function HYPromise() {}
三. 构造函数的规划
class HYPromise {
constructor(executor) {
// 定义状态
// 定义resolve、reject回调
// resolve执行微任务队列:改变状态、获取value、then传入执行成功回调
// reject执行微任务队列:改变状态、获取reason、then传入执行失败回调
// try catch
executor(resolve, reject)
}
}
四. then方法的实现
class HYPromise {
then(onFulfilled, onRejected) {
// this.onFulfilled = onFulfilled
// this.onRejected = onRejected
// 1.判断onFulfilled、onRejected,会给默认值
// 2.返回Promise resolve/reject
// 3.判断之前的promise状态是否确定
// onFulfilled/onRejected直接执行(捕获异常)
// 4.添加到数组中push(() => { 执行 onFulfilled/onRejected 直接执行代码})
}
}
五. catch方法
class HYPromise {
catch(onRejected) {
return this.then(undefined, onRejected)
}
}
六. finally
class HYPromise {
finally(onFinally) {
return this.then(() => {onFinally()}, () => {onFinally()})
}
}
七. resolve/reject
八. all/allSettled
核心:要知道new Promise的resolve、reject在什么情况下执行
all:
- 情况一:所有的都有结果
- 情况二:有一个reject
allSettled:
- 情况:所有都有结果,并且一定执行resolve
九.race/any
race:
- 情况:只要有结果
any:
- 情况一:必须等到一个resolve结果
- 情况二:都没有resolve,所有的都是reject
实现代码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFunctionWithCatchError(execFn, value, resolve, reject) {
try {
const result = execFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class HYPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
const defaultOnRejected = (err) => {
throw err;
};
onRejected = onRejected || defaultOnRejected;
const defaultOnFulfilled = (value) => {
return value;
};
onFulfilled = onFulfilled || defaultOnFulfilled;
return new HYPromise((resolve, reject) => {
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled)
this.onFulfilledFns.push(() => {
execFunctionWithCatchError(
onFulfilled,
this.value,
resolve,
reject
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject
);
});
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(onFinally) {
this.then(
() => {
onFinally();
},
() => {
onFinally();
}
);
}
static resolve(value) {
return new HYPromise((resolve) => resolve(value));
}
static reject(reason) {
return new HYPromise((resolve, reject) => reject(reason));
}
static all(promises) {
return new HYPromise((resolve, reject) => {
const values = [];
promises.forEach((promise) => {
promise.then(
(res) => {
values.push(res);
if (values.length === promises.length) {
resolve(values);
}
},
(err) => {
reject(err);
}
);
});
});
}
static allSettled(promises) {
return new HYPromise((resolve) => {
const results = [];
promises.forEach((promise) => {
promise.then(
(res) => {
results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
if (results.length === promises.length) {
resolve(results);
}
},
(err) => {
results.push({ status: PROMISE_STATUS_REJECTED, value: err });
if (results.length === promises.length) {
resolve(results);
}
}
);
});
});
}
static race(promises) {
return new HYPromise((resolve, reject) => {
promises.forEach((promise) => {
// promise.then(res => {
// resolve(res)
// }, err => {
// reject(err)
// })
promise.then(resolve, reject);
});
});
}
static any(promises) {
// resolve必须等到有一个成功的结果
// reject所有的都失败才执行reject
const reasons = [];
return new HYPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve, (err) => {
reasons.push(err);
if (reasons.length === promises.length) {
reject(new AggregateError(reasons));
}
});
});
});
}
}
十一、迭代器和生成器
迭代器
-
迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
- 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
- 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;
-
从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。
-
在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):
- 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
- 那么在js中这个标准就是一个特定的next方法;
-
next方法有如下的要求:
-
一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
-
done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
-
value
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略
-
// 编写的一个迭代器
const iterator = {
next: function() {
return { done: true, value: 123 }
}
}
// 数组
const names = ["abc", "cba", "nba"]
// 创建一个迭代器对象来访问数组
let index = 0
const namesIterator = {
next: function() {
if (index < names.length) {
return { done: false, value: names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
console.log(namesIterator.next()) // { done: false, value: "nba" }
console.log(namesIterator.next()) // { done: true, value: undefined }
可迭代对象
-
但是上面的代码整体来说看起来是有点奇怪的:
- 我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;
- 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;
-
什么又是可迭代对象呢?
- 它和迭代器是不同的概念;
- 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
- 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性;
-
当我们要问一个问题,我们转成这样的一个东西有什么好处呢?
- 当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的@@iterator 方法;
// 创建一个迭代器对象来访问数组
const iterableObj = {
names: ["abc", "cba", "nba"],
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
原生迭代器对象
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:
- String、Array、Map、Set、arguments对象、NodeList集合;
const names = ["abc", "cba", "nba"]
console.log(names[Symbol.iterator])
const iterator1 = names[Symbol.iterator]()
console.log(iterator1.next())
console.log(iterator1.next())
for (const item of names) {
console.log(item)
}
// Map/Set
const set = new Set()
set.add(10)
set.add(100)
set.add(1000)
console.log(set[Symbol.iterator])
for (const item of set) {
console.log(item)
}
// 函数中arguments也是一个可迭代对象
function foo(x, y, z) {
console.log(arguments[Symbol.iterator])
for (const arg of arguments) {
console.log(arg)
}
}
foo(10, 20, 30)
可迭代对象的应用
那么这些东西可以被用在哪里呢?
- JavaScript中语法:for ...of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
- 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
- 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
// 1.for of场景
// 2.展开语法(spread syntax)
const iterableObj = {
names: ["abc", "cba", "nba"],
[Symbol.iterator]: function () {
let index = 0
return {
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
const names = ["abc", "cba", "nba"]
const newNames = [...names, ...iterableObj]
console.log(newNames)
const obj = { name: "why", age: 18 }
// for (const item of obj) {
// }
// ES9(ES2018)中新增的一个特性: 用的不是迭代器
const newObj = { ...obj }
console.log(newObj)
// 3.解构语法
const [name1, name2] = names
// const { name, age } = obj 不一样ES9新增的特性
// 4.创建一些其他对象时
const set1 = new Set(iterableObj)
const set2 = new Set(names)
const arr1 = Array.from(iterableObj)
// 5.Promise.all
Promise.all(iterableObj).then(res => {
console.log(res)
})
自定义类的迭代
在前面我们看到Array、Set、String、Map等类创建出来的对象都是可迭代对象:
- 在面向对象开发中,我们可以通过class定义一个自己的类,这个类可以创建很多的对象:
- 如果我们也希望自己的类创建出来的对象默认是可迭代的,那么在设计类的时候我们就可以添加上@@iterator 方法;
// 案例: 创建一个教室类, 创建出来的对象都是可迭代对象
class Classroom {
constructor(address, name, students) {
this.address = address
this.name = name
this.students = students
}
entry(newStudent) {
this.students.push(newStudent)
}
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.students.length) {
return { done: false, value: this.students[index++] }
} else {
return { done: true, value: undefined }
}
},
return: () => {
console.log("迭代器提前终止了~")
return { done: true, value: undefined }
}
}
}
}
const classroom = new Classroom("3幢5楼205", "计算机教室", ["james", "kobe", "curry", "why"])
classroom.entry("lilei")
for (const stu of classroom) {
console.log(stu)
if (stu === "why") break
}
/*
james
kobe
curry
why
迭代器提前终止了~
*/
迭代器的中断
-
迭代器在某些情况下会在没有完全迭代的情况下中断:
- 比如遍历的过程中通过break、continue、return、throw中断了循环操作;
- 比如在解构的时候,没有解构所有的值;
-
那么这个时候我们想要监听中断的话,可以添加return方法
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.students.length) {
return { done: false, value: this.students[index++] }
} else {
return { done: true, value: undefined }
}
},
return: () => {
console.log("迭代器提前终止了~")
return { done: true, value: undefined }
}
}
}
生成器
-
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
-
平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。
-
生成器函数也是一个函数,但是和普通的函数有一些区别:
-
首先,生成器函数需要在function的后面加一个符号:*
-
其次,生成器函数可以通过yield关键字来控制函数的执行流程:
-
最后,生成器函数的返回值是一个Generator(生成器):
- 生成器事实上是一种特殊的迭代器;
-
生成器函数执行
我们发现上面的生成器函数foo的执行体压根没有执行,它只是返回了一个生成器对象。
- 那么我们如何可以让它执行函数中的东西呢?调用next即可;
- 我们之前学习迭代器时,知道迭代器的next是会有返回值的;
- 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果;
// 当遇到yield时候值暂停函数的执行
// 当遇到return时候生成器就停止执行
function* foo() {
console.log("函数开始执行~")
const value1 = 100
console.log("第一段代码:", value1)
yield value1
const value2 = 200
console.log("第二段代码:", value2)
yield value2
const value3 = 300
console.log("第三段代码:", value3)
yield value3
console.log("函数执行结束~")
return "123"
}
// 调用生成器函数时, 会给我们返回一个生成器对象
// generator本质上是一个特殊的iterator
const generator = foo()
// 开始执行第一段代码
console.log("返回值1:", generator.next())
console.log("返回值2:", generator.next())
console.log("返回值3:", generator.next())
console.log("返回值3:", generator.next())
/*
函数开始执行~
第一段代码: 100
返回值1: { value: 100, done: false }
第二段代码: 200
返回值2: { value: 200, done: false }
第三段代码: 300
返回值3: { value: 300, done: false }
函数执行结束~
返回值3: { value: '123', done: true }
*/
生成器传递参数 – next函数
函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?
- 答案是可以的;
- 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;
- 注意:也就是说我们是为本次的函数代码块执行提供了一个值;
function* foo(num) {
console.log("函数开始执行~")
const value1 = 100 * num
console.log("第一段代码:", value1)
const n = yield value1
const value2 = 200 * n
console.log("第二段代码:", value2)
const count = yield value2
const value3 = 300 * count
console.log("第三段代码:", value3)
yield value3
console.log("函数执行结束~")
return "123"
}
// 生成器上的next方法可以传递参数
const generator = foo(5)
console.log(generator.next())
// 第二段代码, 第二次调用next的时候执行的
console.log(generator.next(10))
console.log(generator.next(25))
/*
函数开始执行~
第一段代码: 500
{ value: 500, done: false }
第二段代码: 2000
{ value: 2000, done: false }
第三段代码: 7500
{ value: 7500, done: false }
*/
生成器提前结束 – return函数
还有一个可以给生成器函数传递参数的方法是通过return函数:
- return传值后这个生成器函数就会结束,之后调用next不会继续生成值了;
function* foo(num) {
console.log("函数开始执行~")
const value1 = 100 * num
console.log("第一段代码:", value1)
const n = yield value1
const value2 = 200 * n
console.log("第二段代码:", value2)
const count = yield value2
const value3 = 300 * count
console.log("第三段代码:", value3)
yield value3
console.log("函数执行结束~")
return "123"
}
const generator = foo(10)
console.log(generator.next())
// 第二段代码的执行, 使用了return
// 那么就意味着相当于在第一段代码的后面加上return, 就会提前终端生成器函数代码继续执行
console.log(generator.return(15))
console.log(generator.next())
console.log(generator.next())
/*
函数开始执行~
第一段代码: 1000
{ value: 1000, done: false }
{ value: 15, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
*/
生成器抛出异常 – throw函数
除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常:
- 抛出异常后我们可以在生成器函数中捕获异常;
- 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行;
function* foo() {
console.log("代码开始执行~")
const value1 = 100
try {
yield value1
} catch (error) {
console.log("捕获到异常情况:", error)
yield "abc"
}
console.log("第二段代码继续执行")
const value2 = 200
yield value2
console.log("代码执行结束~")
}
const generator = foo()
const result = generator.next()
generator.throw("error message")
/*
代码开始执行~
捕获到异常情况: error message
*/
生成器替代迭代器
-
我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
-
事实上我们还可以使用yield*来生产一个可迭代对象:
- 这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;
// 1.生成器来替代迭代器
function* createArrayIterator(arr) {
// 3.第三种写法 yield*
yield* arr
// 2.第二种写法
for (const item of arr) {
yield item
}
// 1.第一种写法
yield "abc" // { done: false, value: "abc" }
yield "cba" // { done: false, value: "abc" }
yield "nba" // { done: false, value: "abc" }
}
创建一个函数, 这个函数可以迭代一个范围内的数字:
// 2.创建一个函数, 这个函数可以迭代一个范围内的数字
// 10 20
function* createRangeIterator(start, end) {
let index = start
while (index < end) {
yield index++
}
在之前的自定义类迭代中,我们也可以换成生成器:
// 3.class案例
class Classroom {
constructor(address, name, students) {
this.address = address
this.name = name
this.students = students
}
entry(newStudent) {
this.students.push(newStudent)
}
foo = () => {
console.log("foo function")
}
// [Symbol.iterator] = function*() {
// yield* this.students
// }
*[Symbol.iterator]() {
yield* this.students
}
}
const classroom = new Classroom("3幢", "1102", ["abc", "cba"])
for (const item of classroom) {
console.log(item)
}
/*
abc
cba
*/
异步处理方案
-
学完了我们前面的Promise、生成器等,我们目前来看一下异步代码的最终处理方案。
-
需求:
- 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;
- 第二次的请求url依赖于第一次的结果;
- 第三次的请求url依赖于第二次的结果;
- 依次类推;
// request.js
function requestData(url) {
// 异步请求的代码会被放入到executor中
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
resolve(url)
}, 2000);
})
}
// 需求:
// 1> url: why -> res: why
// 2> url: res + "aaa" -> res: whyaaa
// 3> url: res + "bbb" => res: whyaaabbb
// 1.第一种方案: 多次回调
// 回调地狱
requestData("why").then(res => {
requestData(res + "aaa").then(res => {
requestData(res + "bbb").then(res => {
console.log(res)
})
})
})
// 2.第二种方案: Promise中then的返回值来解决
requestData("why").then(res => {
return requestData(res + "aaa")
}).then(res => {
return requestData(res + "bbb")
}).then(res => {
console.log(res)
})
// 3.第三种方案: Promise + generator实现
function* getData() {
const res1 = yield requestData("why")
const res2 = yield requestData(res1 + "aaa")
const res3 = yield requestData(res2 + "bbb")
const res4 = yield requestData(res3 + "ccc")
console.log(res4)
}
自动执行generator函数
-
目前我们的写法有两个问题:
- 第一,我们不能确定到底需要调用几层的Promise关系;
- 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?
-
所以,我们可以封装一个工具函数execGenerator自动执行生成器函数:
function* getData() {
const res1 = yield requestData("why")
const res2 = yield requestData(res1 + "aaa")
const res3 = yield requestData(res2 + "bbb")
const res4 = yield requestData(res3 + "ccc")
console.log(res4)
}
// 1> 手动执行生成器函数
const generator = getData()
generator.next().value.then(res => {
generator.next(res).value.then(res => {
generator.next(res).value.then(res => {
generator.next(res)
})
})
})
// 2> 自己封装了一个自动执行的函数
function execGenerator(genFn) {
const generator = genFn()
function exec(res) {
const result = generator.next(res)
if (result.done) {
return result.value
}
result.value.then(res => {
exec(res)
})
}
exec()
}
execGenerator(getData)
// 3> 第三方包co自动执行
const co = require('co')
co(getData)
// 4.第四种方案: async/await
async function getData() {
const res1 = await requestData("why")
const res2 = await requestData(res1 + "aaa")
const res3 = await requestData(res2 + "bbb")
const res4 = await requestData(res3 + "ccc")
console.log(res4)
}
getData()
异步函数 async function
-
async关键字用于声明一个异步函数:
- async是asynchronous单词的缩写,异步、非同步;
- sync是synchronous单词的缩写,同步、同时;
-
async异步函数可以有很多中写法:
// await/async
async function foo1() {
}
const foo2 = async () => {
}
class Foo {
async bar() {
}
}
异步函数的执行流程
-
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
-
异步函数有返回值时,和普通函数会有区别:
- 情况一:异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中;
- 情况二:如果我们的异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定;
- 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;
async function foo() {
console.log("foo function start~")
console.log("中间代码~")
console.log("foo function end~")
// 1.返回一个值
// return aaa
// 2.返回thenable
return {
then: function(resolve, reject) {
resolve("hahahah")
}
}
// 3.返回Promise
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("hehehehe")
}, 2000)
})
}
// 异步函数的返回值一定是一个Promise
const promise = foo()
promise.then(res => {
console.log("promise then function exec:", res)
})
/*
foo function start~
中间代码~
foo function end~
promise then function exec: hehehehe
*/
- 如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;
async function foo() {
console.log("foo function start~")
console.log("中间代码~")
// 异步函数中的异常, 会被作为异步函数返回的Promise的reject值的
throw new Error("error message")
console.log("foo function end~")
}
// 异步函数的返回值一定是一个Promise
foo().catch(err => {
console.log("coderwhy err:", err)
})
console.log("后续还有代码~~~~~")
await关键字
-
async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
-
await关键字有什么特点呢?
- 通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
- 那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
-
如果await后面是一个普通的值,那么会直接返回这个值;
-
如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;
-
如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值;
// 1.await更上表达式
function requestData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(222)
reject(1111)
}, 2000);
})
}
// async function foo() {
// const res1 = await requestData()
// console.log("后面的代码1", res1)
// console.log("后面的代码2")
// console.log("后面的代码3")
// const res2 = await requestData()
// console.log("res2后面的代码", res2)
// }
// 2.跟上其他的值
async function foo() {
// const res1 = await 123
// const res1 = await {
// then: function(resolve, reject) {
// resolve("abc")
// }
// }
const res1 = await new Promise((resolve) => {
resolve("why")
})
console.log("res1:", res1)
}
// 3.reject值
async function foo() {
const res1 = await requestData()
console.log("res1:", res1)
}
foo().catch(err => {
console.log("err:", err)
})
十二、事件循环
进程和线程
-
线程和进程是操作系统中的两个概念:
- 进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式;
- 线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中;
-
听起来很抽象,这里还是给出我的解释:
- 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
- 线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程;
- 所以我们也可以说进程是线程的容器;
-
再用一个形象的例子解释:
- 操作系统类似于一个大工厂;
- 工厂中里有很多车间,这个车间就是进程;
- 每个车间可能有一个以上的工人在工厂,这个工人就是线程;
-
操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
- 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
- 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
- 对于用户来说是感受不到这种快速的切换的
浏览器中的JavaScript线程
-
我们经常会说JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。
-
浏览器是一个进程吗,它里面只有一个线程吗?
- 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
- 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;
-
JavaScript的代码执行是在一个单独的线程中执行的:
- 这就意味着JavaScript的代码,在同一个时刻只能做一件事;
- 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;
-
所以真正耗时的操作,实际上并不是由JavaScript线程在执行的:
- 浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作;
- 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;
浏览器的事件循环
-
如果在执行JavaScript代码的过程中,有异步操作呢?
- 中间我们插入了一个setTimeout的函数调用;
- 这个函数被放到入调用栈中,执行会立即结束,并不会阻塞后续代码的执行;
宏任务和微任务
-
但是事件循环中并非只维护着一个队列,事实上是有两个队列:
- 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
- 微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()等
-
那么事件循环对于两个队列的优先级是怎么样的呢?
-
main script中的代码优先执行(编写的顶层script代码);
-
在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
- 也就是宏任务执行之前,必须保证微任务队列是空的;
- 如果不为空,那么就优先执行微任务队列中的任务(回调);
-
setTimeout(() => {
console.log("setTimeout")
}, 1000)
queueMicrotask(() => {
console.log("queueMicrotask")
})
Promise.resolve().then(() => {
console.log("Promise then")
})
function foo() {
console.log("foo")
}
function bar() {
console.log("bar")
foo()
}
bar()
console.log("其他代码")
// bar
// foo
// 其他代码
// queueMicrotask
// Promise then
// setTimeout
Node的事件循环
-
浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。
-
这里我们来给出一个Node的架构图:
- 我们会发现libuv中主要维护了一个EventLoop和worker threads(线程池);
- EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等
-
libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit、Julia、pyuv等其他地方;
Node事件循环的阶段
-
我们最前面就强调过,事件循环像是一个桥梁,是连接着应用程序的JavaScript和系统调用之间的通道:
- 无论是我们的文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
- 事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
-
但是一次完整的事件循环Tick分成很多个阶段:
- 定时器(Timers) :本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
- 待定回调(Pending Callback) :对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收到 ECONNREFUSED。
- idle, prepare :仅系统内部使用。
- 轮询(Poll) :检索新的 I/O 事件;执行与 I/O 相关的回调;
- 检测(check) :setImmediate() 回调函数在这里执行。
- 关闭的回调函数**:一些关闭的回调函数,如:socket.on('close', ...)。
Node的宏任务和微任务
-
我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务:
- 宏任务(macrotask):setTimeout、setInterval、IO事件、setImmediate、close事件;
- 微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;
-
但是,Node中的事件循环不只是 微任务队列和 宏任务队列:
-
微任务队列:
- next tick queue:process.nextTick;
- other queue:Promise的then回调、queueMicrotask;
-
宏任务队列:
- timer queue:setTimeout、setInterval;
- poll queue:IO事件
- check queue:setImmediate;
- close queue:close事件;
-
-
所以,在每一次事件循环的tick中,会按照如下顺序来执行代码:
- next tick microtask queue;
- other microtask queue;
- timer queue;
- poll queue;
- check queue;
- close queue;
Promise面试题
第一题
setTimeout(function () {
console.log("setTimeout1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("then1");
});
setTimeout(function () {
console.log("setTimeout2");
});
console.log(2);
queueMicrotask(() => {
console.log("queueMicrotask1")
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});
// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2
第二题
async function async1() {
console.log('async1 start')
await async2();
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1')
resolve();
}).then(function () {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
第三题
Promise.resolve().then(() => {
console.log(0);
// 1.•••直接return一个值 相当于resolve(4)
// return 4
// 2.return thenable的值
return {
then: function(resolve) {
// 大量的计算
resolve(4)
}
}
// 3.return Promise
// 不是普通的值, 多加一次微任务
// Promise.resolve(4), 多加一次微任务
// 一共多加两次微任务
return Promise.resolve(4)
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() =>{
console.log(6);
})
// 1.return 4
// 0
// 1
// 4
// 2
// 3
// 5
// 6
// 2.return thenable
// 0
// 1
// 2
// 4
// 3
// 5
// 6
// 3.return promise
// 0
// 1
// 2
// 3
// 4
// 5
// 6
第四题(node环境)
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2