前端异步(async)解决方案(所有方案)

2401_84153158 2024-08-15 15:03:01 阅读 55

**缺点:**整个程序都要变成事件驱动型,运行流程会变得不清晰。

事件鉴定方法:

(1).onclick方法:

element.οnclick=function(){ -- -->

//处理函数

}

优点:写法兼容到主流浏览器。

缺点:当同一个element元素绑定多个事件时,只有最后一个事件会被添加。

例如:

element.οnclick=handler1;

element.οnclick=handler2;

element.οnclick=handler3;

上诉只有handler3会被添加执行,所以我们使用另外一种方法添加事件。(2)attachEvent和addEvenListener方法

(2).attachEvent和addEvenListener方法:

//IE:attachEvent(IE下的事件监听)

elment.attachEvent(“onclick”,handler1);

elment.attachEvent(“onclick”,handler2);

elment.attachEvent(“onclick”,handler3);

上述三个方法执行顺序:3-2-1;

//标准addEventListener(标准下的监听)

elment.addEvenListener(“click”,handler1,false);

elment.addEvenListener(“click”,handler2,false);

elment.addEvenListener(“click”,handler3,false);>

执行顺序:1-2-3;

PS:该方法的第三个参数是冒泡获取(useCapture),是一个布尔值:当为false时表示由里向外(事件冒泡),true表示由外向里(事件捕获)。

document.getElementById(“id1”).addEventListener(“click”,function(){

console.log(‘id1’);

},false);

document.getElementById(“id2”).addEventListener(“click”,function({

console.log(‘id2’);

},false);

//点击id=id2的div,先在console中输出,先输出id2,在输出id1

document.getElementById(“id1”).addEventListener(“click”,function({

console.log(‘id1’);

},false);

document.getElementById(“id2”).addEventListener(“click”,function({

console.log(‘id2’);

},true);

//点击id=id2的div,先在console中输出,先输出id1,在输出id2

(3).DOM方法addEventListener()和removeListenner():

addEventListenner()和removeListenner()表示用来分配和删除事件的函数。这两种方法都需要三种参数,分别为:string(事件名称),要触发事件的函数function,指定事件的处理函数的时期或者阶段(boolean)。例子见(2)

(4).通用的时间添加方法:

on:function(elment,type,handler){

//添加事件

return element.attachEvent?elment.attachEvent(“on”+type,handler):elment.addEventListener(type,handler,false);

}

事件冒泡和事件捕获的区别,可以参考:

二.工具方案

工具方案大致分为以下5个:

Promise

gengerator函数

async await

node.js中 nextTick setImmidate

第三方库 async.js

下面针对每一个做详细说明应用:

1.Promise(重点)

(1).Promise的含义和发展:

含义:Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。简单点说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。

发展:Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了语法,原生提供了Promise

(2).它的一般形式:

new Promise(

/* executor */

function(resolve, reject) {

if (/* success */) {

// …执行代码

resolve();

} else { /* fail */

// …执行代码

reject();

}

}

);

其中,Promise中的参数executor是一个执行器函数,它有两个参数resolvereject。它内部通常有一些异步操作,如果异步操作成功,则可以调用resolve()来将该实例的状态置为fulfilled,即已完成的,如果一旦失败,可以调用reject()来将该实例的状态置为rejected,即失败的。

我们可以把Promise对象看成是一条工厂的流水线,对于流水线来说,从它的工作职能上看,它只有三种状态,一个是初始状态(刚开机的时候),一个是加工产品成功,一个是加工产品失败(出现了某些故障)。同样对于Promise对象来说,它也有三种状态:pending: 初始状态,也称为未定状态,就是初始化Promise时,调用executor执行器函数后的状态。 **fulfilled:**完成状态,意味着异步操作成功。

**pending:**初始状态,也称为未定状态,就是初始化Promise时,调用executor执行器函数后的状态。

**fulfilled:**完成状态,意味着异步操作成功。

**rejected:**失败状态,意味着异步操作失败。

它只有两种状态可以转化,即

**操作成功:**pending -> fulfilled

**操作失败:**pending -> rejected

注意:并且这个状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending)。

(3).Promise对象的方法(api):

1):Promise.prototype.then(callback)

Promise对象含有then方法,then()调用后返回一个Promise对象,意味着实例化后的Promise对象可以进行链式调用,而且这个then()方法可以接收两个函数,一个是处理成功后的函数,一个是处理错误结果的函数。

如下:

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

// 2秒后置为接收状态

setTimeout(function() {

resolve(‘success’);

}, 2000);

});

promise1.then(function(data) {

console.log(data); // success

}, function(err) {

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

}).then(function(data) {

// 上一步的then()方法没有返回值

console.log(‘链式调用:’ + data); // 链式调用:undefined

}).then(function(data) {

// …

});

在这里我们主要关注promise1.then()方法调用后返回的Promise对象的状态,是pending还是fulfilled,或者是rejected?

返回的这个Promise对象的状态主要是根据promise1.then()方法返回的值,大致分为以下几种情况:

1.如果then()方法中返回了一个参数值,那么返回的Promise将会变成接收状态。

2.如果then()方法中抛出了一个异常,那么返回的Promise将会变成拒绝状态。

3. 如果then()方法调用resolve()方法,那么返回的Promise将会变成接收状态。

4. 如果then()方法调用reject()方法,那么返回的Promise将会变成拒绝状态。

5.如果then()方法返回了一个未知状态(pending)的Promise新实例,那么返回的新Promise就是未知 状态。

6.如果then()方法没有明确指定的resolve(data)/reject(data)/return data时,那么返回的新Promise就是接收状态,可以一层一层地往下传递。

2):Promise.prototype.catch(callback)

catch()方法和then()方法一样,都会返回一个新的Promise对象,它主要用于捕获异步操作时出现的异常。因此,我们通常省略then()方法的第二个参数,把错误处理控制权转交给其后面的catch()函数,如下:

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

setTimeout(function() {

reject(‘reject’);

}, 2000);

});

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

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

图片描述](/uploads/2024/08/15/1723705383286007605.webp)

注意代码的第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

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-K1FstaYs-1715886553320)]

[外链图片转存中…(img-pTEM3D6q-1715886553320)]

[外链图片转存中…(img-hwwu7RL8-1715886553321)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。