一、异步为什么会发生和异步发生的场景:
二、回调函数
2.1回调函数是异步还是同步
回调函数可能是异步也可能是同步,同步回调是阻塞的,异步回调是非阻塞的。正常情况下回调函数是同步的,但当用settimeout和intervaltime函数,事件监听器等,回调函数就是异步的。
【异步回调是“非阻塞的”:高阶函数无需等待回调完成即可完成其执行。高阶函数可确保稍后在特定事件上执行回调。】
JavaScript中的回调函数看这篇就够了 - 疯狂的技术宅 - SegmentFault 思否
回调函数的缺点:
调函数容易串联,但是串联多的时候,不断的嵌套,会导致代码横向发展,难以理解和调试,被称为回调地狱。
代码:
//异步回调函数:
const text = () => {
document.write('hello james')
}
setTimeout(text,1000)
//同步回调函数
function A(callback) {
console.log("A");
callback();//函数A执行了函数B
}
function B() {
console.log("B");
}
A(B);//函数B作为函数A的入参
三、promise
3.1 promise原理:
Promise是JavaScript中一种异步编程的解决方案,用于解决回调地狱和代码可读性差的问题。它是一个包装了异步操作的对象,可以让我们更优雅、清晰地处理异步操作。
3.2 Promise的优点
优化了异步操作的流程控制,避免了回调地狱和嵌套的回调函数。
可以更好地处理异步操作的状态和结果,使代码更加清晰、简洁。
可以使用Promise.all()方法和Promise.race()方法来处理多个异步操作的结果,进一步简化异步编程的过程。
3.3 Promise的缺点
对于初学者来说,可能需要一些时间来理解Promise的概念和使用方法。
需要在异步操作完成后手动调用resolve()或reject()方法,可能会出现遗漏或错误。
无法取消Promise,一旦创建就必须等待其状态发生变化。
在使用.then()方法链式调用多个Promise对象时,如果其中任意一个Promise对象发生错误,整个链式调用都会停止,需要使用.catch()方法来捕获错误。
3.4 Promise的状态
Promise对象有三种状态,分别为pending、resolved和rejected。Promise对象的状态只能从pending转变为resolved或rejected,一旦状态变化,就不会再改变。
3.4.1 pending
初始化状态,即Promise实例创建后的初始状态,既不是成功也不是失败状态。
3.4.2 fulfilled
操作成功的状态,这意味着Promise的一个操作已经成功完成。Promise对象的结果会传递给.then()方法注册的回调函数。
"resolved"可能是对"fulfilled"状态的一个普遍误解,因为一个"resolved"的Promise可能是"fulfilled"或者"rejected"。
3.4.3 rejected
意味着异步操作失败,Promise对象的错误会传递给.catch()方法注册的回调函数
3.5几个promise函数
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
下面是一个具体的例子。
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
上面代码中,promises
是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成fulfilled
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。
下面是另一个例子。
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));
上面代码中,booksPromise
和userPromise
是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations
这个回调函数。
注意,如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
上面代码中,p1
会resolved
,p2
首先会rejected
,但是p2
有自己的catch
方法,该方法返回的是一个新的 Promise 实例,p2
指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch
方法指定的回调函数。
如果p2
没有自己的catch
方法,就会调用Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
Promise.race()
方法的参数与Promise.all()
方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()
方法,将参数转为 Promise 实例,再进一步处理。
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject
,否则变为resolve
。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
上面代码中,如果 5 秒之内fetch
方法无法返回结果,变量p
的状态就会变为rejected
,从而触发catch
方法指定的回调函数。
Promise.any()
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
Promise.any([
fetch('https://v8.dev/').then(() => 'home'),
fetch('https://v8.dev/blog').then(() => 'blog'),
fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => { // 只要有一个 fetch() 请求成功
console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败
console.log(error);
});
只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束。
下面是Promise()
与await
命令结合使用的例子。
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
上面代码中,Promise.any()
方法的参数数组包含三个 Promise 操作。其中只要有一个变成fulfilled
,Promise.any()
返回的 Promise 对象就变成fulfilled
。如果所有三个操作都变成rejected
,那么await
命令就会抛出错误。
Promise.any()
抛出的错误是一个 AggregateError 实例(详见《对象的扩展》一章),这个 AggregateError 实例对象的errors
属性是一个数组,包含了所有成员的错误。
下面是一个例子。
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results instanceof AggregateError); // true
console.log(results.errors); // [-1, Infinity]
});
代码
//promise的语法
let p = new Promise((resolve,reject) => {
console.log("promise本身是同步");
reject("catch是异步");
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})
console.log("想不到吧")
//promise本身是同步,then才是异步
let p = new Promise((resolve,reject) => {
console.log("promise本身是同步");
resolve("then是异步");
}).then((res) => {
console.log(res);
})
console.log("想不到吧");
//promise为了解决异步,避免回调地狱的方法是then
function testP(val) {
return new Promise((resolve, reject) => {
resolve(val);
});
}
testP("0").then(res1 => {
console.log(res1); //输出0
return testP("1");
}).then(res2 => {
console.log(res2); //输出1
return testP("2");
}).then(res3 => {
console.log(res3); //输出2
return testP("3");
}).catch(err => {
console.log(err);
});
创建promise对象时出现了new关键字,就明白了Promise对象其实就是一个构造函数,是用来生成Promise实例的。能看出来构造函数接收了一个函数作为参数,该函数就是Promise构造函数的回调函数,该函数中有两个参数resolve和reject,这两个参数也分别是两个函数!
简单的去理解的话resolve函数的目的是将Promise对象状态变成成功状态,在异步操作成功时调用,将异步操作的结果,作为参数传递出去。reject函数的目的是将Promise对象的状态变成失败状态,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
then方法中返回的也是一个promise示例
四、async和await
4.1 async和promise的关系
- async/await 是消灭异步回调的终极武器
- 但和Promise并不排斥,两者相辅相成
- 执行 async 函数,返回的是 Promsie 对象
- await 相当于 Promise 的 then ,then指的是成功,不指失败
- try…catch 可捕获异常,代替了 Promise的 catch
js四种异步方法(回调函数、Promise、Generator、async/await)_js 异步函数-CSDN博客
async 和 await(详解)_async和await详解-CSDN博客
async/await最详细的讲解_async await-CSDN博客
async/await最详细的讲解_async await-CSDN博客
代码:
//async
async function test(){
return 'hello async';
}
test().then((val) => {
console.log(val); //hello async
})
//await
/*
* 传入参数n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n+200,这个值将用于下一步骤
*/
function takeLongTime(n){
return new Promise((resolve) => {
setTimeout(() => resolve(n + 200),n);
})
}
function step1(n){
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n){
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n){
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
async function doIt() {
console.time('doIt');
let time1 = 300;
let time2 = await step1(time1);//将Promise对象resolve(n+200)的值赋给time2
let time3 = await step1(time2);
let result = await step1(time3);
console.log(`result is ${result}`);
console.timeEnd('doIt');
}
doIt();
//执行结果为:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1512.904296875ms