2024年Web前端最全前端异步(async)解决方案(所有方案),带你彻底搞懂前端启动速度优化

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

分享一些前端面试题以及学习路线给大家

promise3.then(function(data) {

console.log(‘这里是fulfilled状态’); // 这里不会触发

// …

}).catch(function(err) {

// 最后的catch()方法可以捕获在这一条Promise链上的异常

console.log(‘出错:’ + err); // 出错:reject

});

3):Promise.all()

Promise.all()接收一个参数,它必须是可以迭代的,比如数组。

它通常用来处理一些并发的异步操作,即它们的结果互不干扰,但是又需要异步执行。它最终只有两种状态:成功或者失败。

指的是将数组中所有的任务执行完成之后, 才执行.then 中的任务

它的状态受参数内各个值的状态影响,即里面状态全部为fulfilled时,它才会变成fulfilled,否则变成rejected。

成功调用后返回一个数组,数组的值是有序的,即按照传入参数的数组的值操作后返回的结果。

如下:

const p1 = new Promise((resolve,reject)=>{

setTimeout(()=>{

resolve(console.log(‘p1 任务1’))

},1000)

})

.then( data => {

console.log(‘p1 任务2’)

})

.then( res => {

console.log(‘p1 任务3’)

})

.catch( err =>{ throw err} )

const p2 = new Promise((resolve,reject)=>{

resolve(console.log(‘p2 任务1’))

}).then(

data => {

console.log(‘p2 任务2’)

}

).catch(

err => {

throw err

}

)

//只有在p1,p2都执行完后才会执行then里的内容

Promise.all([p1,p2])

.then(()=>console.log(‘done’))

4):Promise.race()

Promise.race()和Promise.all()类似,都接收一个可以迭代的参数,但是不同之处是Promise.race()的状态变化不是全部受参数内的状态影响,一旦参数内有一个值的状态发生的改变,那么该Promise的状态就是改变的状态。就跟race单词的字面意思一样,谁跑的快谁赢。如下:

var p1 = new Promise(function(resolve, reject) {

setTimeout(resolve, 300, ‘p1 doned’);

});

var p2 = new Promise(function(resolve, reject) {

setTimeout(resolve, 50, ‘p2 doned’);

});

var p3 = new Promise(function(resolve, reject) {

setTimeout(reject, 100, ‘p3 rejected’);

});

Promise.race([p1, p2, p3]).then(function(data) {

// 显然p2更快,所以状态变成了fulfilled

// 如果p3更快,那么状态就会变成rejected

console.log(data); // p2 doned

}).catch(function(err) {

console.log(err); // 不执行

});

5):Promise.resolve()

Promise.resolve()接受一个参数值,可以是普通的值,具有then()方法的对象和Promise实例。正常情况下,它返回一个Promise对象,状态为fulfilled。但是,当解析时发生错误时,返回的Promise对象将会置为rejected态。如下:

// 参数为普通值

var p4 = Promise.resolve(5);

p4.then(function(data) {

console.log(data); // 5

});

// 参数为含有then()方法的对象

var obj = {

then: function() {

console.log(‘obj 里面的then()方法’);

}

};

var p5 = Promise.resolve(obj);

p5.then(function(data) {

// 这里的值时obj方法里面返回的值

console.log(data); // obj 里面的then()方法

});

// 参数为Promise实例

var p6 = Promise.resolve(7);

var p7 = Promise.resolve(p6);

p7.then(function(data) {

// 这里的值时Promise实例返回的值

console.log(data); // 7

});

// 参数为Promise实例,但参数是rejected态

var p8 = Promise.reject(8);

var p9 = Promise.resolve(p8);

p9.then(function(data) {

// 这里的值时Promise实例返回的值

console.log(‘fulfilled:’+ data); // 不执行

}).catch(function(err) {

console.log(‘rejected:’ + err); // rejected: 8

});

6):Promise.reject()

Promise.reject()和Promise.resolve()正好相反,它接收一个参数值reason,即发生异常的原因。此时返回的Promise对象将会置为rejected态。如下:

var p10 = Promise.reject(‘手动拒绝’);

p10.then(function(data) {

console.log(data); // 这里不会执行,因为是rejected态

}).catch(function(err) {

console.log(err); // 手动拒绝

}).then(function(data) {

// 不受上一级影响

console.log(‘状态:fulfilled’); // 状态:fulfilled

});

总之,除非Promise.then()方法内部抛出异常或者是明确置为rejected态,否则它返回的Promise的状态都是fulfilled态,即完成态,并且它的状态不受它的上一级的状态的影响。

2.gengerator函数

在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

es6 提供的 generator函数

总得来说就三点:

*在function关键字后加一个* , 那么这个函数就称之为generator函数

*函数体有关键字 yield , 后面跟每一个任务 , 也可以有return关键字, 保留一个数据

*通过next函数调用, 几个调用, 就是几个人任务执行

(1).简单使用

Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字

function* showWords() {

yield ‘one’;

yield ‘two’;

return ‘three’;

}

var show = showWords();

show.next() // {done: false, value: “one”}

show.next() // {done: false, value: “two”}

show.next() // {done: true, value: “three”}

show.next() // {value: underfined, done: true}

如上代码,定义了一个showWords的生成器函数,调用之后返回了一个迭代器对象(即show)

调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)

每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了

当然还有以下情况:(next()数量小于yield)

function* g1(){

yield ‘任务1’

yield ‘任务2’

yield ‘任务3’

return ‘任务4’

}

const g1done = g1()

console.log(g1done.next()) //{ value: ‘任务1’, done: false }

console.log(g1done.next()) //{ value: ‘任务2’, done: false }

(2).yield和yield*

有时候,我们会看到yield之后跟了一个*号,它是什么,有什么用呢?

类似于生成器前面的*号,yield后面的星号也跟生成器有关,举个大栗子:

function* showWords() {

yield ‘one’;

yield showNumbers();

return ‘three’;

}

function* showNumbers() {

yield 10 + 1;

yield 12;

}

var show = showWords();

show.next() // {done: false, value: “one”}

show.next() // {done: false, value: showNumbers}

show.next() // {done: true, value: “three”}

show.next() // {done: true, value: undefined}

增添了一个生成器函数,我们想在showWords中调用一次,简单的 yield showNumbers()之后发现并没有执行函数里面的yield 10+1

因为yield只能原封不动地返回右边运算后值,但现在的showNumbers()不是一般的函数调用,返回的是迭代器对象

所以换个yield* 让它自动遍历进该对象

function* showWords() {

yield ‘one’;

yield* showNumbers();

return ‘three’;

}

function* showNumbers() {

yield 10 + 1;

yield 12;

}

var show = showWords();

show.next() // {done: false, value: “one”}

show.next() // {done: false, value: 11}

show.next() // {done: false, value: 12}

show.next() // {done: true, value: “three”}

要注意的是,这yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错

function showWords() {

yield ‘one’; // Uncaught SyntaxError: Unexpected string

}

虽然换成yield*不会直接报错,但使用的时候还是会有问题,因为’one’字符串中没有Iterator接口,没有yield提供遍历

function showWords() {

yield* ‘one’;

}

var show = showWords();

show.next() // Uncaught ReferenceError: yield is not defined

在爬虫开发中,我们常常需要请求多个地址,为了保证顺序,引入Promise对象和Generator生成器函数,看这个简单的栗子:

var urls = [‘url1’, ‘url2’, ‘url3’];

function* request(urls) {

urls.forEach(function(url) {

yield req(url);

});

// for (var i = 0, j = urls.length; i < j; ++i) {

// yield req(urls[i]);

// }

}

var r = request(urls);

r.next();

function req(url) {

var p = new Promise(function(resolve, reject) {

$.get(url, function(rs) {

resolve(rs);

});

});

p.then(function() {

r.next();

}).catch(function() {

});

}

上述代码中forEach遍历url数组,匿名函数内部不能使用yield关键字,改换成注释中的for循环就行了

(3).next()调用中的传参

参数值有注入的功能,可改变上一个yield的返回值,如

function* showNumbers() {

var one = yield 1;

var two = yield 2 * one;

yield 3 * two;

}

var show = showNumbers();

show.next().value // 1

show.next().value // NaN

show.next(2).value // 6

第一次调用next之后返回值one为1,但在第二次调用next的时候one其实是undefined的,因为generator不会自动保存相应变量值,我们需要手动的指定,这时two值为NaN,在第三次调用next的时候执行到yield 3 * two,通过传参将上次yield返回值two设为2,得到结果

另一个栗子:

由于ajax请求涉及到网络,不好处理,这里用了setTimeout模拟ajax的请求返回,按顺序进行,并传递每次返回的数据

var urls = [‘url1’, ‘url2’, ‘url3’];

function* request(urls) {

var data;

for (var i = 0, j = urls.length; i < j; ++i) {

data = yield req(urls[i], data);

}

}

var r = request(urls);

r.next();

function log(url, data, cb) {

setTimeout(function() {

cb(url);

}, 1000);

}

function req(url, data) {

var p = new Promise(function(resolve, reject) {

log(url, data, function(rs) {

if (!rs) {

reject();

} else {

resolve(rs);

}

});

});

p.then(function(data) {

console.log(data);

r.next(data);

}).catch(function() {

});

}

达到了按顺序请求三个地址的效果,初始直接r.next()无参数,后续通过r.next(data)将data数据传入

在这里插入图片描述

注意代码的第16行,这里参数用了url变量,是为了和data数据做对比

因为初始next()没有参数,若是直接将url换成data的话,就会因为promise对象的数据判断 !rs == undefined 而reject

所以将第16行换成 cb(data || url);

通过模拟的ajax输出,可了解到next的传参值,第一次在log输出的是 url = 'url1’值,后续将data = 'url1’传入req请求,在log中输出 data = 'url1’值

(4).for…of循环代替.next()

除了使用.next()方法遍历迭代器对象外,通过ES6提供的新循环方式for…of也可遍历,但与next不同的是,它会忽略return返回的值,如

function* showNumbers() {

yield 1;

yield 2;

return 3;

}

var show = showNumbers();

for (var n of show) {

console.log(n) // 1 2

}

此外,处理for…of循环,具有调用迭代器接口的方法方式也可遍历生成器函数,如扩展运算符…的使用

function* showNumbers() {

yield 1;

yield 2;

return 3;

}

var show = showNumbers();

[…show] // [1, 2, length: 2]

更多使用可以参考:MDN - Generator

3.async await (重点)

es7新增的 async函数

可以更舒适地与promise协同工作,它叫做async/await,它是非常的容易理解和使用。

(1).格式

async function aa(){

await ‘任务1’

await ‘任务2’

}

async:

让我们先从async关键字说起,它被放置在一个函数前面。就像下面这样:

async function timeout() {

return ‘hello world’;

}

函数前面的async一词意味着一个简单的事情:这个函数总是返回一个promise,如果代码中有return <非promise>语句,JavaScript会自动把返回的这个value值包装成promise的resolved值。

例如,上面的代码返回resolved值为1的promise,我们可以测试一下:

async function f() {

return 1

}

f().then(alert) // 弹出1

我们也可以显式的返回一个promise,这个将会是同样的结果

async function f() {

return Promise.resolve(1)

}

f().then(alert) // 弹出1

所以,async确保了函数返回一个promise,即使其中包含非promise,这样都不需要你来书写繁杂的Promise,够简单了吧?但是不仅仅只是如此,还有另一个关键词await,只能在async函数里使用,同样,它也很cool。

await:

// 只能在async函数内部使用

let value = await promise

关键词await可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行。

以下是一个promise在1s之后resolve的例子:

async function f() {

let promise = new Promise((resolve, reject) => {

setTimeout(() => resolve(‘done!’), 1000)

})

let result = await promise // 直到promise返回一个resolve值(*)

alert(result) // ‘done!’

}

f()

**函数执行到(await)行会‘暂停’,不再往下执行,**当promise处理完成后重新恢复运行, resolve的值成了最终的result,所以上面的代码会在1s后输出’done!’

我们强调一下:await字面上使得JavaScript等待,直到promise处理完成,

然后将结果继续下去。这并不会花费任何的cpu资源,因为引擎能够同时做其他工作:执行其他脚本,处理事件等等。

这只是一个更优雅的得到promise值的语句,它比promise更加容易阅读和书写。

注意不:能在常规函数里使用await

如果我们试图在非async函数里使用await,就会出现一个语法错误:

function f() {

let promise = Promise.resolve(1)

let result = await promise // syntax error

}

//Uncaught SyntaxError: await is only valid in async function

如果我们忘记了在函数之前放置async,我们就会得到这样一个错误。如上所述,await只能在async函数中工作。

就以前面几个案例可能还看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

async function testResult() {

let first = await doubleAfter2seconds(30);

let second = await doubleAfter2seconds(50);

let third = await doubleAfter2seconds(30);

console.log(first + second + third);

}

6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。

再来一个看看:先来个问题

readFile(‘./01-Promise.js’) 运行结果是Promise, 但是我们使用 async await之后, 它的结果是具体的数据了?

用到了Node.js里的fs模块,fs模块是文件模块,可以操作文件,readFile()是读一个文件,不了解的乐意看Node.js官方文档

const fs = require(‘fs’)//导入fs模块

const readFile = (filename) =>{

return new Promise((resolve,reject)=>{

fs.readFile(filename,(err,data)=>{

resolve(data.toString())

})

})

}

const asyncFn = async() => {

//const f0 = eadFile(‘./01-Promise.js’) //类似{value: ‘文件内容’, done: false}

const f1 = await readFile(‘./01-Promise.js’) //文件内容

//const f1 = readFile(‘./01-Promise.js’).then(data=>data)

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

分享一些前端面试题以及学习路线给大家

t second = await doubleAfter2seconds(50);

let third = await doubleAfter2seconds(30);

console.log(first + second + third);

}

6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。

再来一个看看:先来个问题

readFile(‘./01-Promise.js’) 运行结果是Promise, 但是我们使用 async await之后, 它的结果是具体的数据了?

用到了Node.js里的fs模块,fs模块是文件模块,可以操作文件,readFile()是读一个文件,不了解的乐意看Node.js官方文档

const fs = require(‘fs’)//导入fs模块

const readFile = (filename) =>{

return new Promise((resolve,reject)=>{

fs.readFile(filename,(err,data)=>{

resolve(data.toString())

})

})

}

const asyncFn = async() => {

//const f0 = eadFile(‘./01-Promise.js’) //类似{value: ‘文件内容’, done: false}

const f1 = await readFile(‘./01-Promise.js’) //文件内容

//const f1 = readFile(‘./01-Promise.js’).then(data=>data)

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

分享一些前端面试题以及学习路线给大家

[外链图片转存中…(img-HtnGSZIW-1714843031631)]

[外链图片转存中…(img-dTmyJtXZ-1714843031631)]

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值