乐趣区

关于javascript:前端异步解决方案大全

javascript 是一门单线程语言, 即一次只能实现一个工作, 若有多个工作要执行, 则必须排队依照队列来执行(前一个工作实现, 再执行下一个工作)。

这种模式执行简略,但随着日后的需要,事务,申请增多,这种单线程模式执行效率必然低下。只有有一个工作执行耗费了很长时间,在这个工夫里前面的工作无奈执行。常见的浏览器无响应 (假死),往往就是因为某一段 Javascript 代码长时间运行(比方死循环),导致整个页面卡在这个中央,其余工作无奈执行。( 弊病)

为了解决这个问题,javascript 语言将工作执行模式分成同步和异步:

同步模式:就是下面所说的一种执行模式,后一个工作期待前一个工作完结,而后再执行,程序的执行程序与工作的排列程序是统一的、同步的。

异步模式:就是每一个工作有一个或多个回调函数(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 申请,比方解决文件等。

长处:简略,容易了解和 部署。

毛病:不利于代码的浏览,和保护,各局部之间高度耦合,流程会很凌乱,而且每一个工作只能指定一个回调函数。

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,在输入 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 个:

<ul><li>Promise</li><li>gengerator 函数 </li><li>async await </li><li>node.js 中 nextTick setImmidate</li><li> 第三方库 async.js</li></ul>

上面针对每一个做具体阐明利用:

1.Promise(重点)

(1).Promise 的含意和倒退:

含意:Promise 对象用于一个异步操作的最终实现(或失败)及其后果值的示意。简略点说,它就是用于解决异步操作的,异步解决胜利了就执行胜利的操作,异步解决失败了就捕捉谬误或者进行后续操作。

<p class=”has-text-align-left has-small-font-size”> 倒退: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:实现状态,意味着异步操作胜利。

<ul><li>pending:初始状态, 也称为未定状态,就是初始化 Promise 时,调用 executor 执行器函数后的状态。</li><li>fulfilled:实现状态,意味着异步操作胜利。</li><li>rejected:失败状态,意味着异步操作失败。</li></ul>

它只有两种状态能够转化,即

<ul><li>操作胜利:pending -> fulfilled</li><li>操作失败:pending -> rejected</li></ul>

<p style=”font-size:14px” class=”has-text-color has-vivid-red-color”>留神: 并且这个状态转化是单向的,不可逆转,曾经确定的状态(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 将会变成回绝状态。

  1. 如果 then()办法调用 resolve()办法,那么返回的 Promise 将会变成接管状态。
  2. 如果 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 &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() {});
}

<p class=”has-text-color has-small-font-size has-vivid-red-color”> 上述代码中 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 &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:

让咱们先从 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)

  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 提取进去了

  1. 通过 Promise + async, 咱们能够把多层函数嵌套(异步执行)的里层函数失去的数据 返回进去

<p style=”font-size:14px”>对于 async/await 总结

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

<ul><li> 使函数总是返回一个 promise</li><li> 容许在这其中应用 await</li></ul>

promise 后面的 await 关键字可能使 JavaScript 期待,直到 promise 解决完结。而后:

<ul><li> 如果它是一个谬误,异样就产生了,就像在那个中央调用了 throw error 一样。</li><li> 否则,它会返回一个后果,咱们能够将它调配给一个值 </li></ul>

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

有了 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)
})
退出移动版