1. 同步异步简介
javascript是一门单线程的语言,不同于java这些语言可以通过开辟一个新的线程达到多线程“同时”工作,JS不能同时进行多个任务和流程。
同步和异步其本质都是在只有一条流水线,也就是单线程,它们之间的差异就是在这个流水线上各个流程的执行顺序不同。
同步任务,所有的任务在主线程上排队,只有前一个任务执行完毕,才能执行后一个任务;异步任务,要执行的任务并不进入主线程,而是进入任务队列,等待主线程任务执行完毕,任务队列通知主线程,请求执行任务,此时异步任务才会进入主线程执行。
所谓的异步就是一件事情并不是连续完成的,可以分为多个阶段完成的,任务执行一段后会暂停,转而执行其他任务,过段时间又会再次执行这个任务,然后循环这样的任务执行切换,直至任务完成。
相反,同步是指一个任务是无间断的连续的执行完成的,上一个任务不完成,下一个任务永远无法开始。
2. promise对象
2.1 基本介绍
因为异步任务是在同步任务执行之后才会执行,故我们很难预测到何时才会将异步任务执行完毕,为了找到异步任务执行完毕后进行操作,JavaScript起初引入了回调函数和事件的解决方案。但回调函数在进行多层嵌套后就会产生回调地域问题,给程序开发者和js解析器带来了很大的挑战。
ES6引入了Promise对象,并统一了用法。Promise对象简单的说,就是一个容器,对象中保存着某个未来才会结束的事件结果。Promise可以获取异步操作的消息,同时统一了API,保证了各种异步操作都可以使用同一种方法进行处理。
Promise对象的状态只受异步执行结果的影响,状态有pending(进行中)、fulfilled(已成功)、rejected(已失败),一旦对象的状态转为fulfilled或rejected,就意味着状态不能在改变。promise可以将异步操作以同步操作的方式表达出来,避免了层层嵌套的回调函数。
2.2 基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
// resolve和reject两个函数由JS引擎提供,不用自己部署
let promise = new Promise((resolve,reject) => {
if(){
resolve('successed!');
}else{
reject('error');
}
});
// promise实例完成状态转换后,使用then方法指定resolve和reject状态的回调函数
promise.then((value) => {
/*scuccess*/},(error) => {
/*error*/
});
// catch是then方法的别名,用于指定发生错误时的回调函数,reject能抛出错误,若resolve后再抛出错误时无效的
// 建议使用promise时使用下面的书写方式
promise.then(value => {
})
.catch(error => {
});
// Promise.all();方法用于将多个Promise实例包装成一个新的Promise实例,
// 只有所有的实例均成功,新实例才会成功,若不是promise实例调用resolve函数
let p = Promise.all([p1,p2]);
// Promise.race()方法同样是将多个promise实例包装成一个新的promise实例
// 多个实例总只要有一个实例率先改变的状态就代表了新实例的状态,若不是promise实例调用resolve函数
Promise.race([p1.p2]);
// Promise.resolve()方法将现有对象转成Promise对象
Promise.resolve('foo') /*等价于*/new Promise(resolve => resolve('foo'););
// Promise.reject()方法与Promise.resolve方法类似,只不多返回的实例状态是rejected
// done()方法用于最后做到处理未能捕捉到的错误,总是处于回调链的尾端,保证万无一失
promise.then()
.catch()
.done();
// finally()方法无论实例最终装填如何均会执行,
// 与donn方法的不同之处是接收一个普通的回调函数作为参数,该函数不管怎样必须执行
promise.then()
.catch()
.done()
.finally();
// Promise.try()可以不用区分同步函数或异步函数,它会使同步函数同步执行,异步函数异步执行,与async相同
Promise.try()
.then()
.catch()
.done()
.finally();
3.Generator函数
3.1 基本介绍
Generator函数是ES6提供了一种异步编程解决方案,语法行为与传统函数完全不同。Generator函数可以理解成是一个状态机,内部封住了多个状态,执行Generator函数会返回一个遍历器对象,而非像普通函数那样返回一个函数执行结果,通过遍历遍历器对象获取函数内部的每一个状态。
Generator函数在形式上与普通函数并没有太大的区别:function命令与函数名之间有一个星号;函数体内部使用yield表达式定义不同的内部状态。
Generator函数调用之后并不立即执行,返回的也不是一个执行结果,而是一个指向内部状态的指针对象,只有在调用next方法才会将指针移向下一个状态,并执行两个状态之间的代码。在指针执行的过程中会产生一个状态结果对象,即{value:当前yield表达式结果,done:false/true},其中value的结果遵循若next函数传入参数,next没传入便执行yield后边的表达式结果,done若遇到最后一个yield或return便会转为true,表示结束当前Generator函数。
function* func (){
yield 状态1;
yidld 状态2;
return;
}
let hw = func();
3.2 基本使用
Generator函数调用之后并不立即执行,返回的也不是一个执行结果,而是一个指向内部状态的指针对象,只有在调用next方法才会将指针移向下一个状态,并执行两个状态之间的代码。在指针移动的过程中会产生一个状态结果对象,即{value:当前yield表达式结果,done:false/true},其中value的结果遵循若next函数传入参数,next没传入便执行yield后边的表达式结果,done若遇到最后一个yield或return便会转为true,表示结束当前Generator函数。
yield表达式若用在表达式中,必须放在小括号内:console.log('yield' + yield 123);
yield表达式本身没有返回值,或者说返回值是undefined,next方法可以带一个参数充当yield表达式的返回值。next方法的参数表示的是上一次yield表达式的返回值,故在第一次执行next方法时传入的参数是无效的,且V8引擎也会自动忽略掉第一次使用next的参数,所以第一次使用next方法不用携带参数,若真的想在第一次使用next方法就传入参数,就在Generator函数外边再包一层。
function* func (){
yield 状态1;
yidld 状态2;
return;
}
let hw = func();
// 遍历器对象遍历方式1:next
hw.next();
hw.next();
hw.next();
// 遍历器对象遍历方式2:for循环,done为true时就会停止,且不包含返回对象
for(let f of hw){
console.log(f); // 注意不能用forEach方式循环,forEach函数参数为普通对象
}
// 扩展运算符,解构赋值,Array.from方法等内部都是遍历器接口,均可将Generator函数返回的Iterator对象当做参数
[...hw];
Array.from(hw);
let [x,y,z] = hw;
原生的JavaScript对象并没有遍历接口,故无法使用for…of循环,但通过Generator函数为它加上接口就可以使用了。
// 方式1:
function* objectEntries(obj){
let propKeys = Reflect.ownKeys(obj);
for(let propKey of propKeys){
yield [propKey,obj[propKey]];
}
}
for(let [key,value] of objectEntries({
k:val1,k2:val2})){
console.log(key,value);
}
// 方式2
function