定义:Javascript语言的执行环境是"单线程"(single thread,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推)
优缺点:
优点:实现起来比较简单,执行环境相对单纯。
缺点:只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。
结果:浏览器出现假死现象。
解决方案:
Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)
处理异步的方法:
回掉函数(callback)
回调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。
假定有两个函数f1和f2,后者等待前者的执行结果。
f1()
f2()
function f1(callback) {
setTimeout(function () {
// f1的任务代码
callback();
}, 1000);
}
// 执行
f1(f2)
回调不一定是异步
事件监听
采用事件驱动模式。
任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
监听函数有:on,bind,listen,addEventListener,observe。
- 首先,为f1绑定一个事件(采用jquery写法)。
f1.on('done',f2); // 当f1发生done事件,就执行f2
- f1改写
function f1(){
settimeout(function(){
//f1的任务代码
f1.trigger('done'); // 执行完成后,立即触发done事件,从而开始执行f2.
},1000);
}
发布/订阅
假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"
例子:
1、f2向"信号中心"jQuery订阅"done"信号
jQuery.subscribe("done", f2);
2、 f1可以如下图所写
function f1(){
setTimeout(function () {
// f1的任务代码
jQuery.publish("done"); // f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行
}, 1000);
}
3、 f2完成执行后,也可以取消订阅(unsubscribe)
jQuery.unsubscribe("done", f2);
promise
概括:
Promise是异步编程的一种解决方案,可以替代传统的解决方案--回调函数和事件。
主要解决问题:
- 回调地狱,代码难以维护,常常第一个函数的输出时第二个函数的输入这种现象。
- promise可以支持多个并发的请求,获取并发请求中的数据
ES6统一了用法,并原生提供了Promise对象。作为对象,Promise有一下两个特点:
(1)对象的状态不受外界影响。
(2)一旦状态改变了就不会在变,也就是说任何时候Promise都只有一种状态。
promise的状态:
- promise有两种状态:1、等待(pending);2、完成(settled)。
promise会一直处于等待状态,直到它所包装的异步调用返回/超时/结束。
-
这时候promise状态变成完成状态。完成状态分成两类:1、解决(resolved);2、拒绝(rejected)。
-
promise解决(resolved):意味着顺利结束。promise拒绝(rejected)意味着没有顺利结束。
基本用法:
通过Promise的构造函数创建Promise对象。
var promise = new Promise(function(resolve,reject)
setTimeout(function(){
console.log("hello world");},2000);
});
总结:Promise构造函数接收一个函数作为参数,该函数的两个参数是resolve,reject,它们由JavaScript引擎提供。其中resolve函数的作用是当Promise对象转移到成功,调用resolve并将操作结果作为其参数传递出去;reject函数的作用是单Promise对象的状态变为失败时,将操作报出的错误作为其参数传递出去。
Promise的方法:
- then
promise的then方法带有以下三个参数:成功回调,失败回调,前进回调,一般情况下只需要实现第一个,后面是可选的。Promise中最为重要的是状态,通过then的状态传递可以实现回调函数链式操作的实现。
function greet(){
var promise = new Promise(function(resolve,reject){
var greet = "hello world";
resolve(greet);
});
return promise;
}
var p = greet().then(v=>{
console.log(v); // promise
})
console.log(p); hello world
解析: 可以看出promise执行then还是一个promise,并且Promise的执行是异步的,因为hello world在最后一条输出语句的前面就打印出来,且Promise的状态为pending(进行中)。
- all
Promise的all方法提供了并行执行异步操作的能力,在all中所有异步操作结束后才执行回调。
function p1(){
var promise1 = new Promise(function(resolve,reject){
console.log("p1的第一条输出语句");
console.log("p1的第二条输出语句");
resolve("p1完成");
})
return promise1;
}
function p2(){
var promise2 = new Promise(function(resolve,reject){
console.log("p2的第一条输出语句");
setTimeout(()=>{console.log("p2的第二条输出语句");resolve("p2完成")},2000);
})
return promise2;
}
function p3(){
var promise3 = new Promise(function(resolve,reject){
console.log("p3的第一条输出语句");
console.log("p3的第二条输出语句");
resolve("p3完成")
});
return promise3;
}
Promise.all([p1(),p2(),p3()]).then(function(data){
// 三个都成功则成功
console.log(data);
},function(){
// 只要有失败,则失败
}
})
答案:
p1的第一条输出语句
p1的第二条输出语句
p2的第一条输出语句
p3的第一条输出语句
p3的第二条输出语句
p2的第二条输出语句
['p1完成','p2完成','p3完成']
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
- race()
const p = Promise.race([p1, p2, p3]);
解析:只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
- catch用法
相当于reject状态
//promise
var p=new Promise(function(resolved))
//在这里进行处理。也许可以使用ajax
setTimeout(function(){
var result=10*5;
if(result===50){
resolve(50);
}else{
reject(new Error('Bad Math'));
}
},1000);
});
p.then(function(result){
console.log('Resolve with a values of %d',result);
});
p.catch(function(){
console.error('Something went wrong');
});
上述代码的解释:
1、代码的 关键在于setTimeout()的调用。
2、重要的是,他调用了函数resolve()和reject()。resolve()函数告诉promise用户promise已解决;reject()函数告诉promise用户promise未能顺利完成。
3、另外还有一些使用了promise代码。注意then和catch用法,可以将他们想象成onsucess和onfailure事件的处理程序。
4、巧妙地方是,我们将promise处理与状态分离。也就是说,我们可以调用p.then(或者p.catch)多少次都可以,不管promise是什么状态。
5、promise是ES6管理异步代码的标准方式,javascript库使用promise管理ajax,动画,和其他典型的异步交互。
promise代码题解析
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
reject('error')
}, 1000)
})
promise.then((res)=>{
console.log(res)
},(err)=>{
console.log(err)
})
输出结果:success
解题思路:Promise状态一旦改变,无法在发生变更。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
输出结果:1
解题思路:Promise的then方法的参数期望是函数,传入非函数则会发生值穿透。
setImmediate(function(){
console.log(1);
},0);
setTimeout(function(){
console.log(2);
},0);
new Promise(function(resolve){
console.log(3);
resolve();
console.log(4);
}).then(function(){
console.log(5);
});
console.log(6);
process.nextTick(function(){
console.log(7);
});
console.log(8);
答案:3 4 6 8 7 5 2 1
解析: 优先级:
process.nextTick > promise.then > setTimeout > setImmediate
Promise.resolve(1)
.then((res) => {
console.log(res);
return 2;
})
.catch((err) => {
return 3;
})
.then((res) => {
console.log(res);
});
答案:1,2
解题思路:Promise首先resolve(1),接着就会执行then函数,因此会输出1,然后在函数中返回2。因为是resolve函数,因此后面的catch函数不会执行,而是直接执行第二个then函数,因此会输出2。
async/await
async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
核心原理:
当调用一个 async 函数时,会返回一个 Promise 对象。当这个 async 函数返回一个值时,Promise 的 resolve 方法会负责传递这个值;当 async 函数抛出异常时,Promise 的 reject 方法也会传递这个异常值。
async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复async函数的执行并返回解析值(resolved)。
async的作用:(返回一个Promise对象)
sync function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result); // Promise { 'hello async' }
testAsync().then(v => console.log(v))
// "hello async"
async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
async函数内部return语句返回的值,会成为then方法回调函数的参数。
await的作用:
await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。
使用await的注意点
- await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
- 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
- await命令只能用在async函数之中,如果用在普通函数,就会报错。
经典面试题:
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
解析:
1、定义一个异步函数 async1
2、定义一个异步函数 async2
3、打印 ‘script start’ // *1
4、定义一个定时器(宏任务,优先级低于微任务),在0ms 之后输出
5、执行异步函数 async1
-
5.1、打印 'async1 start' // *2
-
5.2、遇到await 表达式,执行 await 后面的 async2
5.2.1、打印 'async2' // *3
-
5.3返回一个 Promise,跳出 async1 函数体
6、执行 new Promise 里的语句
- 6.1、打印 ‘promise1‘ // *4
- 6.2、resolve() , 返回一个 Promise 对象,把这个 Promise 压进队列里
7、打印 ’script end' // *5
8、同步栈执行完毕
9、回到 async1 的函数体,async2 函数没有返回 Promise,所以把要等async2 的值 resolve,把 Promise 压进队列
10、执行 new Promise 后面的 .then,打印 ’promise2‘ // *6
11、回到 async1 的函数体,await 返回 Promise.resolve() ,然后打印后面的 ’async1 end‘ // *7
12、最后执行定时器(宏任务) setTimeout,打印 ’setTimeout‘ // *8