文章目录
1. 回调地狱
-
定义:回调函数中 嵌套 回调函数的情况就叫做回调地狱。
-
优点:回调函数能使 需要异步操作 的代码 按照顺序执行。
-
缺点: 代码的可读性变差!
-
代码示例
//地狱回调
setTimeout(function () { //第一层
console.log('张三');//等3秒打印张三在执行下一个回调函数
setTimeout(function () { //第二层
console.log('李四');//等2秒打印李四在执行下一个回调函数
setTimeout(function () { //第三层
console.log('王五');//等一秒打印王五
}, 1000)
}, 2000)
}, 3000)
2. 回调地狱的解决方案
2.1. Promise
2.1.1. Promise 的基本使用
- Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。
- Promise 是用户自己创建的,Promise 构造函数接受一个 异步函数(异步任务) 作为参数,该异步函数要有 resolve, reject 两个参数,用于改变 Promise 的 状态 ,与成功与失败的返回值!
- then 函数是 Promise 的执行函数执行成功是的回调函数,
- catch 函数是 Promise 的执行函数执行失败是时的回调函数,
- finally 函数无论函数执行成功或者失败时,finally 都会执行,
let p = new Promise((resolve, reject) => {
// resolve('成功') //走了成功就不会走失败
// throw new Error('失败了');
// reject('failed') // 走了失败就不会走成功
setTimeout(() => {
resolve('success')
}, 0)
})
p.then(res => {
console.log(res);
}, err => {
console.log(err);
}).catch(err => {
console.log(err);
}).finally( () =>{
console.log('finally executor running ....')
}
)
console.log(p);
2.1.2. Promise 源码
class myPromise {
constructor(executor) {
this.state = 'pending'; //状态值
this.value = undefined; //成功的返回值
this.reason = undefined; //失败的返回值
this.onResolveCallbacks = []; //成功的回调函数
this.onRejectCallbacks = []; //失败的回调函数
//成功
let resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fullFilled'
this.value = value
this.onResolveCallbacks.forEach(fn => fn())
}
}
//失败
let reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectCallbacks.forEach(fn => fn())
}
}
try {
//执行函数
executor(resolve, reject)
} catch (err) {
//失败则直接执行reject函数
reject(err)
}
}
then(onFullfilled, onRejected) {
// then 返回一个promise ,这样就是一个递归
let promise2 = new myPromise((resolve, reject) => {
let x;
if (this.state === 'fullFilled') {
//同步,状态为fullfilled,执行 onFullfilled,传入成功的值
setTimeout(() => {
try {
x = onFullfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
//中间任何一个环节报错都走reject
reject(error)
}
}, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
}
if (this.state === 'rejected') {
//异步,rejected,执行 onRejected,传入失败的值
setTimeout(() => {
try {
x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
//中间任何一个环节报错都走reject
reject(error)
}
}, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
}
//异步
if (this.state === 'pending') {
this.onResolveCallbacks.push(() => {
setTimeout(() => {
try {
x = onFullfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
//中间任何一个环节报错都走reject
reject(error)
}
}, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
})
this.onRejectCallbacks.push(() => {
setTimeout(() => {
try {
x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
//中间任何一个环节报错都走reject
reject(error)
}
}, 0)//同步无法使用promise2,所以借用setTimeout异步的方式。
})
}
})
return promise2;//返回promise
}
finally(callback) {
return this.then(callback, callback)
}
catch(rejectFuc) {
return this.then(null, rejectFuc);
}
}
// 这个方法的主要左右是用来判断x的值,如果x的值是一个普通的值,就直接返回x的值,如果x的值是一个promise,就要返回x.then()执行的结果,核心代码如下
const resolvePromise = (promise2, x, resolve, reject) => {
// x和promise2不能是同一个,如果是同一个就报错
if (promise2 === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<promise>')
)
}
// x 为调用链中上一个 promise 中 onFullfilled 方法返回的结果
// 判断 x 是否是一个对象,判断函数是否是对象的方法有: typeof instanceof constructor toString
if (typeof x === 'object' && x != null || typeof x === 'function') {
let called;
try {
let then = x.then; //取 then可以报错,报错就走 reject();
if (typeof then === 'function') {
then.call(x, y => {
console.log('y', y);
if (called) return;
called = true;
resolve(y); //采用promise的成功结果,并且向下传递
resolvePromise(promise2, y, resolve, reject)
}, r => {
if (called) return;
called = true;
reject(r);//采用promise的成功结果,并且向下传递
})
} else {
resolve(x); //x不是一个函数,是一个对象
}
} catch (error) {
if (called) return;
called = true;
reject(error);//报错就走 reject();
}
} else {
// x 不是对象与functin 则 x 是一个普通值, 直接返回
resolve(x)
}
}
myPromise.resolve = (value) => {
return new myPromise((resolve, reject) => {
resolve(value)
})
}
myPromise.reject = (value) => {
return new myPromise((resolve, reject) => {
reject(value)
})
}
// all 方法:全部成功返回一个数组,失败就返回失败的值
myPromise.all = (values) => {//传入的是一个数组 [p1,p2]
return new myPromise((resolve, reject) => {
let results = [];
let i = 0;
let processData = (value, index) => {
results[index] = value;
i++;
if (i === values.length) {
resolve(results)
}
}
for (let i = 0; i < values.length; i++) {
//获取当前的函数
let current = values[i];
//判断是函数还是变量
if (typeof current === 'function' || (typeof current === 'object' && typeof current !== null)) {
if (typeof current.then === 'function') {
current.then(y => {
processData(y, i)
}, err => {
reject(err); //只要有一个传入的promise没有执行成功就走 reject
return;
})
} else {
//常量
processData(values[i], i)
}
}
}
})
}
// race 方法:先快执行谁,然后返回
myPromise.race = (values) => {//传入的是一个数组[p1,p2]
return new myPromise((resolve, reject) => {
for (let i = 0; i < values.length; i++) {
let current = values[i];
if (typeof current === 'function' || (typeof current === 'object' && typeof current !== null)) {
current.then(y => {
resolve(y)
return
}, err => {
reject(err)
return
})
} else {
resolve(current)
}
}
})
}
2.2. async / await 关键字
2.2.1. async 函数
-
async 函数是函数使用 async 关键字声明的函数
-
async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。
await关键字只在 async 函数内有效。如果你在 async 函数体之外使用它,就会抛出语法错误 SyntaxError 。
-
async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。
-
async 函数可能包含 0 个或者多个 await 表达式。await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// Expected output: "resolved"
}
asyncCall();
-
async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
例如,如下代码:
async function foo() { return 1; }
等价于
function foo() { return Promise.resolve(1); }
-
async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。这样的话,一个不含 await 表达式的 async 函数是会同步运行的。然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。
-
例如,如下代码:
async function foo() { await 1; }
等价于
function foo() { return Promise.resolve(1).then(() => undefined); }
2.2.2. 含有 await 的 async 函数内部语句执行顺序
- 在接下来的例子中,我们将使用 await 执行两次 promise,整个 foo 函数的执行将会被分为三个阶段。
- foo 函数的第一行将会同步执行,await 将会等待 promise 的结束。然后暂停通过 foo 的进程,并将控制权交还给调用 foo 的函数。
- 一段时间后,当第一个 promise 完结的时候,控制权将重新回到 foo 函数内。示例中将会将1(promise 状态为 fulfilled)作为结果返回给 await 表达式的左边即 result1。接下来函数会继续进行,到达第二个 await 区域,此时 foo 函数的进程将再次被暂停。
- 一段时间后,同样当第二个 promise 完结的时候,result2 将被赋值为 2,之后函数将会正常同步执行,将默认返回undefined 。
async function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1")),
);
const result2 = await new Promise((resolve) =>
setTimeout(() => resolve("2")),
);
}
foo();
注意:promise 链不是一次就构建好的,相反,promise 链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。
例如,在下面代码中,即使在 promise 链中进一步配置了 .catch 方法处理,也会抛出一个未处理的 promise 被拒绝的错误。这是因为 p2 直到控制从 p1 返回后才会连接到 promise 链。
async function foo() {
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
const results = [await p1, await p2]; // 不推荐使用这种方式,请使用 Promise.all 或者 Promise.allSettled
}
foo().catch(() => {}); // 捕捉所有的错误...
2.2.3. await 串行和并行
function resolveAfter2Seconds() {
console.log("starting slow promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
console.log("slow promise is done");
}, 2000);
});
}
function resolveAfter1Second() {
console.log("starting fast promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("fast");
console.log("fast promise is done");
}, 1000);
});
}
async function sequentialStart() {
console.log("==SEQUENTIAL START==");
// 1. Execution gets here almost instantly
const slow = await resolveAfter2Seconds();
console.log(slow); // 2. this runs 2 seconds after 1.
const fast = await resolveAfter1Second();
console.log(fast); // 3. this runs 3 seconds after 1.
}
async function concurrentStart() {
console.log("==CONCURRENT START with await==");
const slow = resolveAfter2Seconds(); // starts timer immediately
const fast = resolveAfter1Second(); // starts timer immediately
// 1. Execution gets here almost instantly
console.log(await slow); // 2. this runs 2 seconds after 1.
console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}
function concurrentPromise() {
console.log("==CONCURRENT START with Promise.all==");
return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
(messages) => {
console.log(messages[0]); // slow
console.log(messages[1]); // fast
},
);
}
async function parallel() {
console.log("==PARALLEL with await Promise.all==");
// Start 2 "jobs" in parallel and wait for both of them to complete
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
}
sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"
// wait above to finish
setTimeout(concurrentStart, 4000); // after 2 seconds, logs "slow" and then "fast"
// wait again
setTimeout(concurrentPromise, 7000); // same as concurrentStart
// wait again
setTimeout(parallel, 10000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"
await 和并行
在 sequentialStart 中,程序在第一个 await 停留了 2 秒,然后又在第二个 await 停留了 1 秒。直到第一个计时器结束后,第二个计时器才被创建。程序需要 3 秒执行完毕。
在 concurrentStart 中,两个计时器被同时创建,然后执行 await。这两个计时器同时运行,这意味着程序完成运行只需要 2 秒,而不是 3 秒,即最慢的计时器的时间。
但是 await 仍旧是顺序执行的,第二个 await 还是得等待第一个执行完。在这个例子中,这使得先运行结束的输出出现在最慢的输出之后。
如果你希望并行执行两个或更多的任务,你必须像在parallel中一样使用await Promise.all([job1(), job2()])。