原文地址:banggan.github.io/2019/08/26/…javascript
javascript
是一门单线程的语言,也就是说一次只能完成一件任务,若是有多个任务,就须要排队进行处理。若是一个任务耗时很长,后面的任务也必须排队等待,这样大大的影响了整个程序的执行。为了解决这个问题,javascript
语言将任务分为两种模式:java
本文主要针对近两年javascript
的发展,主要介绍异步处理的进化史。目前,在javascript
异步处理中,有如下几种方式:jquery
回调函数是最先解决异步编程的方法。不管是常见的
setTimeout
仍是ajax
请求,都是采用回调的形式把事情在某一固定的时刻进行执行。git
 //常见的:setTimeout
 setTimeout(function callback(){
  console.log('aa');
}, 1000);
//ajax请求
ajax(url,function callback(){
console.log("ajax success",res);
})
复制代码
回调函数的处理通常将函数
callback
做为参数传进函数,在合适的时候被调用执行。回调函数的优势就是简单、容易理解和实现,但有个致命的缺点,容易出现回调地狱(Callback hell),即多个回调函数嵌套使用。形成代码可读性差、可维护性差且只能在回调中处理异常。github
ajax(url, () => {
//todo
ajax(url1, () => {
//todo
ajax(url2, () => {
//todo
})
})
})
复制代码
事件监听采用的是事件驱动的模式。事件的执行不取决于代码的顺序,而是某个事件的发生。ajax
假设有两个函数,为f1绑定一个事件(jQuery
的写法),当f1函数发生success
事件时,执行函数f2:编程
f1.on('success',f2);
复制代码
对f1进行改写:数组
function f1(){
ajax(url,() => {
//todo
f1.trigger('success');//触发success事件,从而执行f2函数
})
}
复制代码
事件监听的方式较容易理解,能够绑定多个事件,每一个事件能够指定多个回调函数,并且能够"去耦合",有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。promise
咱们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其余任务能够向信号中心"订阅"(subscribe)这个信号,从而知道何时本身能够开始执行。这就叫作 发布/订阅模式(publish-subscribe pattern),又称**观察者模式"(observer pattern) **。并发
//利用jquery的插件实现
//首先,f2向消息中心订阅success事件
jQuery.subscribe('success',f2);
//对f1进行改写:
function f1(){
ajax(url,() => {
//todo
jQuery.publish('success');//当f1执行完毕后,向消息中心jQuery发布success事件,从而执行f2函数
})
}
//f2执行完毕后,能够取消订阅
jQuery.unsubscribe('success',f2)
复制代码
该方法和事件监听的性质相似,但咱们能够经过消息中心来查阅一共有多少个信号,每一个信号有多少个订阅者。
**Promise**是CommonJS工做组提出的一种规范,能够获取异步操做的消息,也是异步处理中经常使用的一种解决方案。Promise的出现主要是用来解决回调地狱、支持多个并发的请求,获取并发请求的数据而且解决异步的问题。
let p = new Promise((resolve, reject) => {
//作一些异步操做
setTimeout(()=>{
let num = parseInt(Math.random()*100);
if(num > 50){
resolve("num > 50"); // 若是数字大于50就调用成功的函数,而且将状态变成Resolved
}else{
reject("num <50");// 不然就调用失败的函数,将状态变成Rejected
}
},10000)
});
p.then((res) => {
console.log(res);
}).catch((err) =>{
console.log(err);
})
复制代码
Promise
有三种状态:等待pending
、成功fulfied
、失败rejected
;状态一旦改变,就不会再变化,在Promise
对象建立后,会立刻执行。等待状态能够变为fulfied
状态并传递一个值给相应的状态处理方法,也可能变为失败状态rejected
并传递失败信息。任一一种状况出现时,Promise
对象的then
方法就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,均为 Function。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法)。须要注意的是:
Promise.prototype.then
和Promise.prototype.catch
方法返回promise 对象, 因此能够被链式调用,以下图:
Promise
的方法:
Promise.all(iterable)
:谁执行得慢,以谁为准执行回调。返回一个promise
对象,只有当iterable
里面的全部promise
对象成功后才会执行。一旦iterable
里面有promise
对象执行失败就触发该对象的失败。对象在触发成功后,会把一个包iterable
里全部promise
返回值的数组做为成功回调的返回值,顺序跟iterable
的顺序保持一致;若是这个新的promise对象触发了失败状态,它会把iterable
里第一个触发失败的promise对象的错误信息做为它的失败错误信息。Promise.all
方法常被用于处理多个promise
对象的状态集合。Promise.race(iterable)
: 谁执行得快,以谁为准执行回调。iterable
参数里的任意一个子promise被成功或失败后,父promise立刻也会用子promise的成功返回值或失败详情做为参数调用父promise绑定的相应句柄,并返回该promise对象。Promise.reject(err)
与Promise.resolve(res)
Generators是ES6提供的异步解决方案,其最大的特色就是能够控制函数的执行。能够理解成一个内部封装了不少状态的状态机,也是一个遍历器对象生成函数。Generator 函数的特征:
function
关键字与函数名之间有一个星号;- 函数体内部使用
yield
表达式,定义不一样的内部状态;
- 经过
yield
暂停函数,next
启动函数,每次返回的是yield
表达式结果。next
能够接受参数,从而实如今函数运行的不一样阶段,能够从外部向内部注入不一样的值。next
返回一个包含value
和done
的对象,其中value
表示迭代的值,后者表示迭代是否完成。举个例子:
function* createIterator(x) {
let y = yield (x+1)
let z = 2*(yield(y/3))
return (x+y+z)
}
// generators能够像正常函数同样被调用,不一样的是会返回一个 iterator
let iterator = createIterator(4);
console.log(iterator.next()); // {value:5,done:false}
console.log(iterator.next()); // {value:NaN,done:false}
console.log(iterator.next()); // {value:NaN,done:true}
let iterator1 = createIterator(4);//返回一个iterator
//next传参数
console.log(iterator1.next()); // {value:5,done:false}
console.log(iterator1.next(12)); // {value:4,done:false}
console.log(iterator1.next(15)); // {value:46,done:true}
复制代码
代码分析:
当不参数时,next的value返回NaN;
当传参数时,做为上一个yeild的值,在第一次使用next时,传参数无效,只有第二次开始,才有效。
第一次执行next时,函数会被暂停在yeild(x+1),因此返回的是4+1=5;
第二次执行next时,传入的12为上一次yeild表达式的值,因此y=12,返回的是12/3=4;
第三次执行next时,传入的15为上一次yeild表达式的值,因此z=30,y=12;x=4,返回30+12+4=46
async/await
在ES7提出,是目前在javascript
异步处理的终极解决方案。
async
其本质是Generator
函数的语法糖。相较于Generator
放入改进以下:
- 内置执行器:Generator 函数的执行必须靠执行器,而
async
函数自带执行器。其调用方式与普通函数如出一辙,不须要调next
方法;- 更好的语义:
async
表示定义异步函数,而await
表示后面的表达式须要等待,相较于*和yeild
更语义化;- 更广的适用性:
co
模块约定,yield
命令后面只能是Thunk
函数或Promise
对象。而async
函数的await
命令后面则能够是Promise
或者 原始类型的值;- 返回
Promise
:async
函数返回值是Promise
对象,比Generator
函数返回的Iterator
对象方便,能够直接使用then()
方法进行链式调用;
async
语法
用来定义异步函数,自动将函数转换为promise
对象,可使用then
来添加回调,其内部return
的值做为then
回调的参数。
async function f(){
return "hello async";
}
f().then((res) => { //经过then来添加回调且内部返回的res做为回调的参数
console.log(res); // hello async
})
复制代码
在异步函数的内部可使用await
,其返回的promise
对象必须等到内部因此await
命令后的promise
对象执行完,才会发生状态变化即执行then
回调。
const delay = function(timeout){
return new Promise(function(resolve){
return setTimeout(resolve, timeout);
});
}
async function f(){
await delay(1000);
await delay(2000);
return '完成';
}
f().then(res => console.log(res));//须要等待3秒以后才会打印:完成
复制代码
await
即表示异步等待,用来暂停异步函数的执行,只能在异步函数和promise
使用,且当使用在promise
前面,表示等待promise
完成并返回结果。
async function f() {
return await 1 //await后面不是Promise的话,也会被转换为一个当即为resolve的promise
};
f().then( res => console.log("处理成功",res))//打印出:处理成功 1
.catch(err => console.log("处理是被",err))////打印出:Promise{<resolved>:undefined}
复制代码
若是
await
后面的异步出现错误,等同于async
返回的promise
对象为reject
,其错误会被catch的回调函数接收到。须要注意的是,当async
函数中只要一个await
出现 reject 状态,则后面的await
都不会被执行。
let a;
async function f(){
await Promise.reject("error")
a = await 1 //该await并无执行
}
err().then(res => console.log(a))
复制代码
怎么处理呢,能够把第一个
await
放在try/catch
,遇到函数的时候,能够将错误抛出并往下执行。
async function f() {
try{
await Promise.reject('error');
}catch(error){
console.log(error);
}
return await 1
}
f().then(res => console.log('成功', res))//成功打印出1
复制代码
若是有多个
await
处理,能够统一放在try/catch
模块中,并且async
可使得try/catch
同时处理同步和异步错误。
经过以上六种
javascript
异步处理的经常使用方法,能够看出async/await
能够说是异步终极解决方案了,最后看一下async/await
用得最多的场景:若是一个业务须要不少个异步操做组成,而且每一个步骤都依赖于上一步的执行结果,这里采用不一样的延时来体现:
//首先定义一个延时函数
function delay(time) {
return new Promise(resolve => {
setTimeout(() => resolve(time), time);
});
}
//采用promise链式调用实现
delay(500).then(result => {
return delay(result + 1000)
}).then(result => {
return delay(result + 2000)
}).then(result => {
console.log(result) //3500ms后打印出3500
}).catch(error => {
console.log(error)
})
//采用async实现
async function f(){
const r1 = await delay(500)
const r2 = await delay(r1+1000)
const r3 = await delay(r2+2000)
return r3
}
f().then(res =>{
console.log(res)
}).catch(err=>{
console.log(err)
})
复制代码
能够看出,采用
promise
实现采用了不少then进行不停的链式调用,使得代码变得冗长和复杂且没有语义化。而async/await
首先使用同步的方法来写异步,代码很是清晰直观,并且使代码语义化,一眼就能看出代码执行的顺序,最后 async 函数自带执行器,执行的时候无需手动加载。