1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > javascript之异步操作理解---回调函数 async await以及promise对象

javascript之异步操作理解---回调函数 async await以及promise对象

时间:2020-02-04 02:16:28

相关推荐

javascript之异步操作理解---回调函数 async await以及promise对象

javascript之异步操作理解---回调函数,async,await以及promise对象

概述

概述

写在前面:虽然平时做项目,但是发现自己写的代码还是很烂。最近接触了一个对性能要求比较高的项目,于是重新开始审视自己写的代码是不是达到尽量较小系统性能了。加上前几天学到express框架了解到了回调函数的操作。作为拓展,查了许多资料,再次总结一下javascript的异步操作。

javascript是一门单线程语言,即一次只能完成一个任务,若有多个任务要执行,则必须排队按照队列来执行(前一个任务完成,再执行下一个任务)。

这种模式执行简单,但随着日后的需求,事务,请求增多,这种单线程模式执行效率必定低下。只要有一个任务执行消耗了很长时间,在这个时间里后面的任务无法执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。(弊端)

为了解决这个问题,javascript语言将任务执行模式分成同步(Synchronous)和异步(Asynchronous):

同步模式:就是上面所说的一种执行模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。

异步模式:就是每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

异步任务图示

“异步模式”非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,”异步模式”甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。(异步模式的重要性)

下面就带来几种前端异步解决方案:

一.传统方案

1.回调函数(callback):

异步编程的基本方法。

首先需要声明,回调函数只是一种实现,并不是异步模式特有的实现。回调函数同样可以运用到同步(阻塞)的场景下以及其他一些场景。

回调函数的定义:

函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

生活举例:约会结束后你送你女朋友回家,离别时,你肯定会说:“到家了给我发条信息,我很担心你。” 然后你女朋友回家以后还真给你发了条信息。其实这就是一个回调的过程。你留了个参数函数(要求女朋友给你发条信息)给你女朋友,然后你女朋友回家,回家的动作是主函数。她必须先回到家以后,主函数执行完了,再执行传进去的函数,然后你就收到一条信息了。

案例:

//定义主函数,回调函数作为参数function A(callback) {callback(); console.log('我是主函数');}//定义回调函数function B(){setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作 }//调用主函数,将函数B传进去A(B);//输出结果我是主函数我是回调函数

上面的代码中,我们先定义了主函数和回调函数,然后再去调用主函数,将回调函数传进去。

定义主函数的时候,我们让代码先去执行callback()回调函数,但输出结果却是后输出回调函数的内容。这就说明了主函数不用等待回调函数执行完,可以接着执行自己的代码。所以一般回调函数都用在耗时操作上面。比如ajax请求,比如处理文件等。

优点:简单,容易理解和 部署。

缺点:不利于代码的阅读,和维护,各部分之间高度耦合,流程会很混乱,而且每一个任务只能指定一个回调函数。

JavaScript回调地狱

当多个异步函数一个接一个地执行时,发生回调地狱。它也被称为厄运金字塔。

假设您要获取所有Github用户的列表。然后,在用户中,您只想搜索JavaScript库的主要贡献者。然后,在用户中,您想要获取姓名为John的人员的详细信息。

为了在回调的帮助下实现此功能,代码片段将如下所示。

http.get('/users', function(users) {/* Display all users */console.log(users);http.get('/repos/javascript/contributors?q=contributions&order=desc', function(contributors) {/* Display all top contributors */console.log(contributors);http.get('/users/Jhon', function(userData) {/* Display user with username 'Jhon' */console.log(userData);});});});

从上面的代码片段中,您可以看到代码变得更加难以理解,难以维护和修改。这是由于所有回调函数的嵌套而发生的。

您如何停止回调地狱?

可以使用多种技术来避免回调地狱,如下所示。

通过使用诺言。

借助async-await

通过使用async.js库

JavaScript回调与闭包

关闭

用技术术语来说,闭包是捆绑在一起的功能的组合,引用了其周围的状态。

简而言之,闭包允许从内部函数访问外部函数的范围。

要使用闭包,我们需要在另一个函数内部定义一个函数。然后,我们需要将其返回或传递给另一个函数。

打回来

从概念上讲,回调类似于闭包。回调基本上是一个函数接受另一个函数作为参数的地方。

2.事件监听

采用事件驱动模式。

任务的执行不取决代码的顺序,而取决于某一个事件是否发生。

监听函数有:on,bind,listen,addEventListener,observe

以f1和f2为例。首先,为f1绑定一个事件(采用jquery写法)。

f1.on('done',f2);

上面代码意思是,当f1发生done事件,就执行f2。

然后对f1进行改写:

function f1(){settimeout(function(){//f1的任务代码f1.trigger('done'); },1000);}

f1.trigger(‘done’)表示,执行完成后,立即触发done事件,从而开始执行f2.

优点:比较容易理解,可以绑定多个事件,每一个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。

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

事件鉴定方法:

(1).onclick方法:

element.onclick=function(){//处理函数}

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

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

例如:

element.onclick=handler1;element.onclick=handler2;element.onclick=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表示由外向里(事件捕获)。

<div id="id1"><div id="id2"></div></div>

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,在输出id1document.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是一个执行器函数,它有两个参数resolve和reject。它内部通常有一些异步操作,如果异步操作成功,则可以调用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更快,那么状态就会变成rejectedconsole.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 &lt; 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 // 1show.next().value // NaNshow.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 &lt; 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一词意味着一个简单的事情:这个函数总是返回一个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)const f2 = await readFile('./02-generator.js') //文件内容console.log( f1 )console.log( f2 )}asyncFn()

readFile()定义了一个Promise方法读取文件,这里有个坑,我们现在是在里面返回出数据了的,要知道这里面有3层函数,如果不用new Promise这个方法,大家可以试试用常规方法能不能返回数据,先透个底拿不到,大家可以试试。

asyncFn()输出了文件内容,在const f1 = eadFile(’./01-Promise.js’) 这一句这一句会打印出出一个Promise{‘文件内容’},有点类似前面的generator函数输出的{value: ‘’, done: false},只不过省略了done,大家知道,我们读文件,肯定是要里面的内容的,如果输出 Promise{‘文件内容’} ,我们是不好取出内容的,但是await很好的帮我们解决了这个问题,前面加上await直接输出了文件内容。

所以:这个问题可以有个小总结

1.async函数使用了generator函数的语法糖 , 它直接生成对象 {value: ‘’,done:false} await 直接将value提取出来了

通过Promise + async,我们可以把多层函数嵌套(异步执行)的里层函数得到的数据 返回出来

关于async/await总结

放在一个函数前的async有两个作用:

使函数总是返回一个promise

允许在这其中使用await

promise前面的await关键字能够使JavaScript等待,直到promise处理结束。然后:

如果它是一个错误,异常就产生了,就像在那个地方调用了throw error一样。

否则,它会返回一个结果,我们可以将它分配给一个值

他们一起提供了一个很好的框架来编写易于读写的异步代码。

有了async/await,我们很少需要写promise.then/catch,但是我们仍然不应该忘记它们是基于promise的,因为有些时候(例如在最外面的范围内)我们不得不使用这些方法。Promise.all也是一个非常棒的东西,它能够同时等待很多任务。

4.node.js nextTick setImmidate

nextTick vs setImmediate

轮询:

nodejs中是事件驱动的,有一个循环线程一直从事件队列中取任务执行或者

I/O的操作转给后台线程池来操作,把这个循环线程的每次执行的过程算是一次轮询.

2.setImmediate()的使用

即时计时器立即执行工作,它是在事件轮询之后执行,为了防止轮询阻塞,每次只会调用一个。

3.Process.nextTick()的使用

它和setImmediate()执行的顺序不一样,它是在事件轮询之前执行,为了防止I/O饥饿,所以有一个默认process.maxTickDepth=1000来限制事件队列的每次循环可执行的nextTick()事件的数目。

总结:

nextTick()的回调函数执行的优先级要高于setImmediate();

process.nextTick()属于idle观察者,setImmediate()属于check观察者.在每一轮循环检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者.

在具体实现上,process.nextTick()的回调函数保存在一个数组中,

setImmediate()的结果则是保存在链表中.

在行为上,process.nextTick()在每轮循环中会将数组中的回调函数全部执行完.

而setImmediate()在每轮循环中执行链表中的一个回调函数.

5.第三方库 async.js

async.js是一个第三方库,带有很多api

暴露了一个async对象,这个对象身上有很多的api(多任务执行),例如parallel,series

async.parallel(&#91;function(callback){callback(null,'任务1')},function(callback){callback(null,'任务2')},],(err,data)=>{console.log('data',data)})

文章摘自:

/p/87457553

/qq_32442973/article/details/89322763

/blog//12/asynchronous%EF%BC%BFjavascript.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。