同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。异步操作执行顺序可详见进阶知识-事件循环机制 。
1. 回调函数 Callback
复制 function f1 (callback){
// ...
callback ();
}
function f2 (){
// ...
}
f1 (f2); // f1若为异步操作,则f2会等待f1执行完毕才运行,正常js引擎遇到异步操作,会挂起,并运行下一个任务
2. 事件监听
异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
具体详见《异步操作-事件监听》 。
3. 发布/订阅
事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式 ”(publish-subscribe pattern),又称“观察者模式 ”(observer pattern)。
具体详见《异步操作-发布订阅》 。
4. 定时器
2.1 setTimeout()
复制 var timerId = setTimeout (func | code , delay);
第一个参数 func|code
是将要推迟执行的函数名或者一段代码,第二个参数 delay
是推迟执行的毫秒数。返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
复制 // 形式1
setTimeout ( 'console.log(2)' , 1000 );
// 形式2
function f () {
console .log ( 2 );
}
setTimeout (f , 1000 );
//形式3 - 后面两个参数为回调函数的参数
setTimeout ( function (a , b) {
console .log (a + b);
} , 1000 , 1 , 1 );
注意,setTimeout 其实还能加入其他的参数,第三个参数及以后的参数都可以作为回调函数的参数。
2.2 setInterval()
与setTimeout()用法一致,setInterval
指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。setTimeout
函数用来指定某个函数或某段代码,在多少毫秒之后执行。
setTimeout
和setInterval
函数都返回一个整数值,表示计数器编号,将该整数传入clearTimeout
和clearInterval
函数,就可以取消对应的定时器。
5. ES6 - Promise*
复制 // Promise 封装异步操作
// 1. 接口封装
const promise = new Promise ((resolve , reject) => {
// 异步代码...
if ( /* 异步操作成功 */ ){
resolve (value);
} else { /* 异步操作失败 */
reject ( new Error ());
}
})
// 或者还可以这么写
function promise () {
return new Promise ((resolve , reject) => {
// 异步代码...
if ( /* 异步操作成功 */ ){
resolve (value);
} else { /* 异步操作失败 */
reject ( new Error ());
}
})
}
// 2. 业务调用接口
promise .then (successFunc , errorFunc)
复制 // 举例
function timeout (ms) {
return new Promise ((resolve , reject) => {
setTimeout (resolve , ms);
}
)
}
timeout ( 100 ) .then (result => {});
Promise 对象一旦新建它就会立即执行,无法中途取消。
then
方法通过链式方法可读性更强,then
方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled
状态)的回调函数,第二个是异步操作失败(变为rejected
)时的回调函数(一般该参数可以省略)
复制 // 写法一 - f3回调函数的参数,是f2函数的运行结果
f1 () .then ( function () {
return f2 ();
}) .then (f3);;
// 写法二 - f3回调函数的参数,是undefined
f1 () .then ( function () {
f2 ();
}) .then (f3);;
// 写法三 - f3回调函数的参数,是f2函数返回的函数的运行结果
f1 () .then ( f2 ()) .then (f3);;
// 写法四 - f3回调函数的参数,是f2函数的运行结果,并且f2会接收到f1()返回的结果
f1 () .then (f2) .then (f3);;
Promise.prototype.catch
方法是 .then(null, rejection)
或 .then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。因此在使用上建议 then 就使用单个参数表示成功回调函数,错误回调函数一律通过 catch 调用。
复制 f1 () .then ( function () {
// ...
}) .catch ( function (error) {
console .log ( '发生错误!' , error);
});
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
复制 promise
.then (result => {···})
.catch (error => {···})
.finally (() => {···});
Promise.all()
并发处理多个异步任务,所有任务都执行成功,才能得到结果。
Promise.race()
并发处理多个异步任务,只要有一个任务执行成功,就能得到结果。
复制 Promise .all ([promise1 , promise2 , promise3]) .then ((result) => {
console .log (result);
});
Promise .all ([promise1 , promise2 , promise3]) .then ((result) => {
console .log (result);
});
链式调用,基于 Promise 处理多层嵌套调用:
复制 const operation1 = () => {
return new Promise ((resolve , reject) => {
setTimeout (() => {
resolve ( 'Operation1 is ok ' )
} , 1000 )
})
}
const operation2 = () => {
return new Promise ((resolve , reject) => {
setTimeout (() => {
resolve ( 'Operation2 is ok ' )
} , 1000 )
})
}
const operation3 = () => {
return new Promise ((resolve , reject) => {
setTimeout (() => {
resolve ( 'Operation3 is ok ' )
} , 1000 )
})
}
operation1 () .then ((res) => {
console .log (res)
return operation2 ()
}) .then ((res) => {
console .log (res)
return operation3 ()
}) .then ((res) => {
console .log (res)
})
return 后面的返回值,有两种情况:
情况 1:返回 Promise 实例对象,则该实例对象会调用下一个 then
情况 2:返回普通值,则直接传递给下一个 then,通过 then 参数中函数的参数接收该值
6. ES6- Generator
Generator 函数是一个状态机,封装了多个内部状态;Generator 函数也是一个遍历器对象生成函数,返回遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
复制 // 注意function后需要加*,函数体内部使用yield表达式,定义不同的内部状态
function* helloWorldGenerator () {
yield 'hello' ;
yield 'world' ;
return 'ending' ;
}
var hw = helloWorldGenerator (); // 该函数有三个状态:hello,world 和 return 语句(结束执行),注意,函数名()并不是函数执行,而是一个指向内部状态的指针对象(遍历器对象)
// 调用遍历器对象的next方法,使得指针移向下一个状态,返回一个有着value和done两个属性的对象,done表示遍历是否结束。yield表达式是暂停执行的标记(还有return语句),next方法可以恢复往下执行。
hw .next () // { value: 'hello', done: false }
hw .next () // { value: 'world', done: false }
hw .next () // { value: 'ending', done: true }
hw .next () // { value: undefined, done: true }
复制 // yield表达式只能用在 Generator 函数里面,即使是 Generator 函数里面的函数
function* f (a){
a .forEach ((item) => {
yield 'hello' ;
})
}
// yield表达式如果用在另一个表达式之中,必须放在圆括号里面
function* demo () {
console .log ( 'Hello' + yield 123 ); // SyntaxError
console .log ( 'Hello' + ( yield 123 )); // OK
}
next 方法可以带参数,该参数就会被当作上一个 yield
表达式的返回值(第一次使用 next
方法时无上一个yeild,则传递参数是无效的),若不带参数,yield表达式对应值为undefined。
复制 function* foo (x) {
var y = 2 * ( yield (x + 1 ));
var z = yield (y / 3 );
return (x + y + z);
}
var a = foo ( 5 );
a .next () // Object{value:6, done:false}
a .next () // Object{value:NaN, done:false} - 无参数返回,则yield表达式对应的值为undefined
a .next () // Object{value:NaN, done:true}
var b = foo ( 5 );
b .next () // { value:6, done:false }
b .next ( 12 ) // { value:8, done:false }
b .next ( 13 ) // { value:42, done:true }
内部调用的是遍历器接口的都可以将 Generator 函数返回的 Iterator 对象作为参数,但是注意返回对象的done
属性为true
,遍历循环就会中止,且不包含该返回对象,也就是说return后面所带的不包含在内。
复制 function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[ ... numbers ()] // [1, 2]
// Array.from 方法
Array .from ( numbers ()) // [1, 2]
// 解构赋值
let [x , y] = numbers ();
x // 1
y // 2
// for...of 循环
for ( let n of numbers ()) {
console .log (n)
}
// 1
// 2
Generator 函数返回的遍历器对象的方法:
next() - 使得指针移向下一个状态,见上面的一些应用
throw() - 在函数体外抛出错误,然后在 Generator 函数体内捕获。
return() - 可以返回给定的值,并且终结遍历 Generator 函数。
这三个方法本质上是同一件事,它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式。
复制 // throw 例子
var g = function* () {
try {
yield ;
} catch (e) {
console .log ( '内部捕获' , e);
}
};
var i = g ();
i .next ();
try {
i .throw ( 'a' );
i .throw ( 'b' );
} catch (e) {
console .log ( '外部捕获' , e);
}
// 内部捕获 a - Generator函数内捕获错误
// 外部捕获 b - 由于Generator函数内捕获错误已经执行过了,因而在外部捕获
// return例子
function* gen () {
yield 1 ;
yield 2 ;
yield 3 ;
}
var g = gen ();
g .next () // { value: 1, done: false }
g .return ( 'foo' ) // { value: "foo", done: true }
g .next () // { value: undefined, done: true }
yield* 表达式用来在一个 Generator 函数里面执行另一个 Generator 函数。
复制 function* foo () {
yield 'a' ;
yield 'b' ;
}
// yield* 表达式等同于部署一个for...of循环
function* bar () {
yield 'x' ;
yield* foo ();
yield 'y' ;
} // 'x' 'a' 'b' 'y'
// 如果不加*则返回的是一个遍历器对象。
function* bar () {
yield 'x' ;
yield foo ();
yield 'y' ;
} // 'x' 一个遍历器对象 'y'
yield* 后面的 Generator函数,没有 return 语句时,相当于 for...of 的一种简写形式;在有 return 语句时,则需要用 var value = yield* iterator 的形式获取 return 语句的值。
复制 function* iter1 (){
yield 'x' ;
yield 'y' ;
}
function* iter2 () {
yield 'a' ;
yield 'b' ;
return 'c' ;
}
function* concat (iter1 , iter2) {
yield* iter1;
const value = yield* iter2;
yield value;
}
const a = concat ( iter1 () , iter2 ()); // x y a b c
实际上,任何数据结构只要有 Iterator 接口,就可以被 yield* 遍历。
复制 // 数组原生支持遍历器,因此可以被yield*遍历
function* gen (){
yield* [ "a" , "b" , "c" ];
}
gen () .next () // { value:"a", done:false }
// 字符串同样
function* gen (){
yield* 'hello' ;
}
gen () .next () // { value:"h", done:false }
7. ES6 - async/await*
async 返回的是一个 promise 对象,如果 return 一个直接量,也会通过 Promise.resolve()
封装成 Promise 对象。await 后面接 Promise 对象,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果;如果后面是不是一个 Promise 对象,则表达式的运算结果就是它等到的东西。
复制 // 例子:指定毫秒后输出内容
function timeout (ms) {
return new Promise ((resolve) => {
setTimeout (resolve , ms);
});
}
async function asyncPrint (value , ms) {
await timeout (ms);
console .log (value);
}
asyncPrint ( 'hello world' , 50 );
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async 函数是 Generator 函数的语法糖,其中几点改进:
Generator 函数的执行必须必须靠执行器 next(),而 async 函数的执行与普通函数一样,只要一行。
async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
Generator 函数中 yield 命令后面只能是 Thunk 函数或 Promise 对象,async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(实际会转成立即 resolved 的 Promise 对象)。
语法的注意点:
async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。
正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
复制 async function myFunction () {
try {
await somethingThatReturnsAPromise ();
} catch (err) {
console .log (err);
}
}
// 另一种写法
async function myFunction () {
await somethingThatReturnsAPromise () .catch ( function (err){
console .log (err);
});
}
链式调用,基于 async/await 处理多层嵌套调用:
复制 const operation1 = () => {
return new Promise ((resolve , reject) => {
setTimeout (() => {
resolve ( 'Operation1 is ok ' )
} , 1000 )
})
}
const operation2 = () => {
return new Promise ((resolve , reject) => {
setTimeout (() => {
resolve ( 'Operation2 is ok ' )
} , 1000 )
})
}
const operation3 = () => {
return new Promise ((resolve , reject) => {
setTimeout (() => {
resolve ( 'Operation3 is ok ' )
} , 1000 )
})
}
async function test () {
const res = await operation1 ()
console .log (res)
const res2 = await operation2 ()
console .log (res2)
const res3 = await operation3 ()
console .log (res3)
}
test ()