异步(Asynchronous)指同一时间不止一个事件发生,或者说是多个相关事件不等待前一事件完成就发生。异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。
现在与将来
一个完整的javascript程序,几乎一定是由多个块构成的。这些块中只有一个是现在执行,其余的则会在将来执行。最常见的块单位是函数。
举个例子
function now() {
return 21;
}
function later() {
answer = answer * 2;
console.log( "Meaning of life:", answer );
}
var answer = now();
setTimeout( later, 1000 );
上面的例子两个块:现在执行的部分,以及将来执行的部分。
现在执行部分
function now() {
return 21;
}
function later() { .. }
var answer = now();
setTimeout( later, 1000 );
将来执行部分
answer = answer * 2;
console.log( "Meaning of life:", answer );
事件循环
异步与事件循环密切相关,在了解解决方案前,建议先看下并发模型与事件循环。
异步编程解决方案
- 回调
- 事件监听
- 发布订阅
- Promise
- Generator
- async/await
注意:Promise、Generator、async/await 在IE浏览器都不支持,需要做兼容处理。
下面介绍常用的解决方案。
1.回调
JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
缺点:大量的嵌套回调会造成回调地狱,难以维护。
// 如果下一请求依赖上一个请求的返回值,就需要不断嵌套
$.ajax({
url: "url-1",
success: function(){
$.ajax({
url: "url-2",
success: function(){
$.ajax({
url: "url-3",
success: function(){
// ...
}
});
}
});
}
});
2.promise(ES6)
介绍
- promise是一个代表了异步操作最终完成或者失败的对象。
- 本质上,promise是一个函数返回的对象, 它可以绑定回调函数
- Promise对象是一个构造函数,用来生成Promise实例。
- 使用promise最直接的好处就是能够使用then进行链式调用
创建
Promise 对象是由关键字 new
及其构造函数来创建的。
var p = new Promise((resolve, reject) => {
// 一系列异步操作
// resolve()
// reject()
});
console.log(p) // Promise
想要某个函数拥有 promise 功能,只需让其返回一个promise即可。
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
};
状态
一个 Promise有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。状态:pengding=>fulfilled
- rejected: 意味着操作失败。状态:pending=>rejected
var p1 = new Promise((resolve, reject) => {
});
console.log(p1); // pending
var p2 = new Promise((resolve, reject) => {
resolve('成功');
});
console.log(p2); // fulfilled
var p3 = new Promise((resolve, reject) => {
reject('失败');
});
console.log(p3); // reject
注意:promise状态是不可逆的。
promise的状态 只有从pengding =》fulfilled 或者 pending =》 rejected,而不会反过来。并且已经resolve的数据,后面无论如何修改,都不会改变then中接受到的数据。
new Promise((resolve, reject) => {
var num = 100;
resolve(num);
num = 999;
resolve(num); // resolve 也不会改变已传出去的num 100
console.log(num) // 999
}).then(result => {
console.log(result) // 100
});
属性
- Promise.length:length属性,值总是为1
- Promise.prototype:构造器原型
方法
iterable:一个可迭代对象,如 Array 或 String。
方法名 | 功能 | 返回结果 | Promise.all(iterable) | 所有传入的 promise 成功才触发成功,只要有一个失败就会触发失败 | 包含所有 promise 值的数组 | Promise.allSettled(iterable) | 所有传入的promise都完成(成功/失败)后完成 | 包含所有 promise 值的数组 | Promise.any(iterable) | 当第一个成功的 promise 成功时返回 | 第一个成功的promise的值 | Promise.race(iterable) | 当第一个 promise 成功/失败返回 | 第一个完成的promise的值 | Promise.reject(reason) | 返回一个状态为失败的 Promise 对象 | 状态为失败的Promise | Promise.resolve(value) | 返回一个状态由给定value决定的 Promise对象 | 状态为成功的Promise |
---|
原型属性
- Promise.prototype.constructor:返回被创建的实例函数. 默认为 Promise 函数.
原型方法
方法名 | 功能 | 返回结果 | 说明 | Promise.prototype.then(resolFun, rejecFun) | 添加 fulfilled 和 rejected 回调到当前 promise | 返回新的 promise | 当回调函数被调用,新 promise 都将以它的返回值来resolve | Promise.prototype.catch(Fun) | 添加一个 rejection 回调到当前 promise | 返回新的 promise | 返回的 promise 会以 rejection 的返回值 resolve | Promise.prototype.finally(Fun) | 当其中的一个 promise 成功时返回 | 返回新的 promise | 无论是fulfilled还是rejected,都会执行 |
---|
promise/A+规范
Promise 规范有很多,如Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+。
ES6 中采用了 Promise/A+ 规范。
Promises/A+规范总结:
- 一个 promise 的当前状态只能是 pending、fulfilled 和 rejected 三种之一。状态改变只能是 pending 到 fulfilled 或者 pending 到rejected。状态改变不可逆。
- promise 的 then 方法接收两个可选参数,表示该 promise 状态改变时的回调。then 方法必须返回一个 promise。then 方法可以被同一个 promise 调用多次。
3.Generator(ES6)
介绍
- Generator 函数是一个状态机,封装了多个内部状态
- 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
- Generator 函数只有调用 next() 方法才会遍历下一个内部状态
关键标识和关键字
- function* : 关键标识
- yield:暂停执行
- yield* :语法糖,在 Generator 函数中执行另一个 Generator 函数
方法
方法名 | 功能 | 备注 | Generator.prototype.next() | 返回一个由 yield表达式生成的值 | Generator.prototype.return() | 返回给定的值并结束生成器 | Generator.prototype.throw() | 向生成器抛出一个错误 |
---|
下面对关键字和各个方法做详细介绍
yield 表达式
- yield 表示暂停执行
- yield 表达式后面的表达式,只有当调用 next()、内部指针指向该语句时才会执行
- yield 表达式的值会作为返回的对象的 value 属性值
- 调用 next() 之前,yield 前面的语句不会执行
function* helloWorldGenerator() {
console.log('aaa')
yield 'hello';
console.log('bbb')
yield 'world';
console.log('ccc')
return 'ending';
}
var hw = helloWorldGenerator();
console.log(hw); // helloWorldGenerator {<suspended>} 状态:suspended
hw.next()
// aaa { value: 'hello', done: false }
hw.next()
// bbb { value: 'world', done: false }
hw.next()
// ccc { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
console.log(hw); // helloWorldGenerator {<closed>} 状态:closed
使用注意:
- yield 只能在 Generator 函数中使用,在其他地方使用会报错。就算是在Generator 函数内,但处于一个普通函数内,也会报错
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
});
};
for (var f of flat(arr)){
console.log(f);
}
// Uncaught SyntaxError: Unexpected identifier
// 改造下
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
var length = a.length;
for (var i = 0; i < length; i++) {
var item = a[i];
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
}
};
for (var f of flat(arr)) {
console.log(f);
}
// 1, 2, 3, 4, 5, 6
- yield表达式如果用在另一个表达式之中,必须放在圆括号里面
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
- yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
next()方法
- next() 表示恢复执行
- next() 可接受参数,并且该参数表示上一个yield表达式的返回值
- 第一次调用 next() 时,传递参数无效。(可封装函数,先执行一次无参 next())
function* G() {
const a = yield 100
console.log('a', a)
const b = yield 200
console.log('b', b)
const c = yield 300
console.log('c', c)
}
var g = G()
g.next(); // {value: 100, done: false}
g.next(); // a undefined {value: 200, done: false}
g.next(); // b undefined {value: 300, done: false}
g.next(); // c undefined {value: undefined, done: true}
g.next(); // {value: 100, done: false}
g.next('aaa'); // a aaa {value: 200, done: false}
g.next('bbb'); // b bbb {value: 300, done: false}
g.next('ccc'); // c ccc {value: undefined, done: true}
throw()方法
throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象。
使用
- throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获
- 如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获
- 如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
- throw 方法可以接受一个参数,该参数会被catch语句接收
- catch 语句只能捕获到第一个 throw 方法
- throw 方法不会中断程序执行,并且会自动执行下一次 next()
优点
多个yield表达式,可以只用一个try...catch代码块来捕获错误
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
var err = i.throw('参数 aaa');
console.log(err)
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 参数 aaa
// {value: undefined, done: true}
// 外部捕获 b
如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获
var g = function* () {
yield;
console.log('内部捕获', e);
};
var i = g();
i.next();
try {
i.throw('参数 aaa');
i.throw('b');
} catch (e) {
console.log('外部捕获', e); // 外部捕获 参数 aaa
}
如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。
var g = function* () {
yield;
console.log('内部捕获', e);
};
var i = g();
i.next();
i.throw();
// VM9627:2 Uncaught undefined
throw 方法不会中断程序执行,并且会自动执行下一次 next()
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
console.log('error')
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // error b
g.next() // c
return()方法
return方法可以返回给定的值,并且终结遍历 Generator 函数。
- 如果 return 方法调用时,不提供参数,则返回值的 value 属性值为 undefined
- 如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会立刻进入finally代码块,执行完以后,整个函数才会结束。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true } =》后面的 done 始终为 true
g.next() // { value: undefined, done: true }
Generator 函数内部有try...finally代码块
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally { // return后依然执行
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
yield* 表达式
yield* 相当于一个语法糖(给后面的可迭代对象部署一个for...of循环),用于执行 Generator 函数内的 Generator 函数。
- yield* 等同于在 Generator 函数内部,部署一个for...of循环
- yield* 返回一个遍历器对象
- 任何可迭代对象都可被 yield* 遍历
// 执行 Generator 函数内的 Generator 函数
function* foo() {
yield 2;
yield 3;
return "foo";
}
function* bar() {
yield 1;
var v = yield* foo();
console.log("v: " + v);
yield 4;
}
var it = bar();
it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
// 任何可迭代对象都可被 yield* 遍历
let read = (function* () {
yield* 'he';
yield* [1, 2, 3];
})();
read.next().value // "h"
read.next().value // "e"
read.next().value // 1
read.next().value // 2
read.next().value // 3
应用
待更新...
4.async/await(ES7)
介绍
- async 函数是 Generator 函数的语法糖,它的写法更趋向于同步。
- async 函数返回一个 promise 对象
- async 函数可以包含0个或者多个 await 指令
- await 会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果
- await 表达之后的代码可以被认为是存在在链式调用的 then 回调方法中
async对Generator的改进
改进点 | async | Generator | 内置执行器 | 调用即执行 | 调用 next() 方法才执行 | 更好的语义 | async(内部有异步操作)、await(等待异步操作完成后) | Generator(生成器)、yield(产出) | 更广的适用性 | await 后可以是Promise 对象和原始数据类型(会被自动转成Promise.resolve()的值) | yield命令后面只能是 Thunk 函数或 Promise 对象 | 返回值是 Promise | 返回 Promise 对象 | 返回 Iterator 对象 |
---|
基本使用
// 异步请求
function resolveAfter1Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('1s resolved');
}, 1000);
});
}
function resolveAfter3Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('3s resolved');
}, 3000);
});
}
async function asyncCall() {
console.log('calling');
const result1 = await resolveAfter3Seconds();
console.log(result1)
const result2 = await resolveAfter1Seconds();
console.log(result2);
return 'ending'
}
asyncCall().then(res => {
console.log(res)
});
// calling
// 3s resolved => 执行3秒后输出
// 1s resolved => 执行4秒后输出
// ending
await命令
- await 命令后是一个 Promise 对象,返回该对象的结果,如果不是则直接返回对应值
- await 命令后的代码可以被认为是存在在链式调用的then回调方法中
async function foo() {
await 1 // 原始数据类型会被自动转成Promise.resolve()的值
}
// 等价于
function foo() {
return Promise.resolve(1).then(() => undefined)
}
错误处理
- await 命令后代码执行失败,并且有 try...catch 捕捉错误,则不会影响接下来的代码执行。
function step(val){
if (val === 2) {
return Promise.reject('出错了')
} else {
return val
}
}
async function main() {
try {
const val1 = await step(1);
const val2 = await step(2);
const val3 = await step(3);
console.log('Final: ', val1, val2, val3);
} catch (err) {
console.error('error', err);
}
console.log('继续执行')
}
main();
// error 出错了
// 继续执行
- await 命令没有 try...catch, async 函数返回的 Promise 对象会被reject,程序中断
function step(val){
if (val === 2) {
return Promise.reject('出错了')
} else {
return val
}
}
async function main() {
const val1 = await step(1);
const val2 = await step(2);
const val3 = await step(3);
console.log('继续执行')
}
main().then().catch(err => {
console.log('error', err)
})
// error 出错了
async 函数的实现原理
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
总结
- JS 异步编程进化史:callback -> promise -> generator -> async + await
- async/await 的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
- async/await 相对于Promise,优势体现在:
- 处理 then 的调用链,能够更清晰准确的写出代码并且也能优雅地解决回调地狱问题。
- async/await 对 Generator 函数的改进,体现在:
- 内置执行器
- 更好的语义
- 更广的适用性
- 返回值是 Promise 对象
- async/await 的缺点:
- 如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。代码没有依赖性的话,可以使用 Promise.all 的方式替代。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!