JS 是单线程的,一次只能执行一个任务,如果中间遇到耗时比较长的任务,剩余代码必须等待它执行完毕才能执行,这样会出现页面假死的情况。为了解决这个问题JS将任务分为同步任务和异步任务。
同步任务:就是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务
异步任务:是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务。
这里说下 JS 为什么单线程的?JS 是浏览器脚本语言,主要用来实现和用户的交互,通过 JS 我们对 DOM 进行操作,如果 JS 是多线程的,一个线程对 DOM 节点内容进行添加,一个线程对同一 DOM 节点进行删除,这样就不好区分是如何操作这个 DOM 节点了,为了简化复杂性,所以 JS 是单线程的。
单线程的优点:实现起来相对简单;单线程的缺点:只要有一个任务耗时比较长,其余任务都需要等待。
# 回调函数
回调函数简单理解就是把函数当成一个参数传给另一个函数,这个当成参数的函数就是回调函数。
function fn1() {}
function fn2(f) {
f();
}
fn1(fn2);
回调函数最大的优点就是兼容性好,缺点是多层嵌套会产生 ”回调地狱“,不利于代码的维护和扩展。在实际项目开发中,下一个请求可能依赖上一次的请求结果,代码可能如下:
// 为了简化代码,ajax 方法省略了 url,error 参数的的处理
function ajax(data, success) {
$.ajax({
url: "/api/getWeather",
data: {
zipcode: data
},
success
});
}
ajax('xx', function(a) {
ajax(a, function(b) {
ajax(b, function(c) {
ajax(c, function(d) {
// ...
})
})
})
})
如果再加上异步操作的失败处理,代码阅读起来将是恐怖的。
ajax('xx', function(a) {
try {
ajax(a, function(b) {
try {
ajax(b, function(c) {
try {
ajax(b, function(c) {
try {
ajax(c, function(d) {
// ...
})
}catch(err) {
}
})
}catch(err) {
}
})
}catch(err) {
}
})
}catch(err) {
}
})
# Promise
ES6 新增的 Promise 很好的解决了 “回调地狱” 的问题,使得我们可以以链式调用的方式来处理代码,避免了大量嵌套。Promise 一共有三种状态,分别是:pending(等待中)、resolve(完成)和reject(拒绝),且状态一旦发生改变就不会再变。Promise 的基本使用可以阅读阮一峰老师的文章 Promise (opens new window),这里不再无脑列举。
// 为了简化代码,ajax 方法省略了 url,error 参数的的处理
function ajax(data, success) {
return new Promise((resolve, reject) => {
$.ajax({
url: "/api/getWeather",
data: {
zipcode: data
},
success: resolve
});
})
}
ajax('xx').then(a => {
return ajax(a)
}).then(b => {
return ajax(b)
}).then(c => {
return ajax(c)
}).then(d => {
return ajax(d)
})
Promise 中处理报错
ajax('xx').then(a => {
return ajax(a)
}).then(b => {
return ajax(b)
}).then(c => {
return ajax(c)
}).then(d => {
return ajax(d)
}).catch(err => {
console.log(err);
})
可以看到,代码从阅读和维护上好了很多。这里我们问一个问题,Promise 是怎么解决 “回调地狱” 问题的呢?
Promise 通过:
- 回调函数延迟绑定
- 返回值穿透
- 错误冒泡
来解决 "回调地狱" 问题。
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'ok')
})
}
ajax().then(res => {
console.log(res)
})
从上面的例子可以看到 Promise 不是没有回调函数,它的回调函数是通过 then 传入的,这里注意下 then ,这方法添加的任务会放到微任务中,等当前的宏任务执行完毕后才会执行这个回调,这也就是 “回调函数延迟绑定”。
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'ok')
})
}
const p = ajax().then(res => {
return ajax()
})
p.then(res => {
console.log(res)
})
从上面的例子可以看出调用 then 方法后,又返回了一个 Promise 我们可以使用这个返回的 Promise 再次进行调用,这便是 “返回值穿透”。
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'ok')
})
}
ajax().then(res => {
return ajax()
}).then(res => {
throw new Error('出错了1')
}).catch(err => {
console.log(err)
})
从上面的例子可以出,前面的错误被后面的 catch 方法捕获了,这也就是 “错误冒泡”,catch 只能捕获它前面的报错,不能捕获后面的报错。
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'ok')
})
}
ajax().then(res => {
return ajax()
}).then(res => {
throw new Error('出错了1')
}).catch(err => {
console.log(err) // Error: 出错了1
}).then(res => {
throw new Error('出错了2') // Uncaught (in promise) Error: 出错了2
})
简单总结下:Promise 使用链式调用解决嵌套问题,使用错误冒泡解决错误处理问题。
在上面说道 “回调函数延迟绑定” 的时候,提到了把 then 方法中的回调函数放到了微任务中,这里需要考虑下这里为什么会用到微任务呢?
首先我们来思考下怎么处理回调问题呢?简单总结无非以下三种方法:
- 使用同步回调,等异步任务执行完再执行后面的任务
- 使用异步回调,将回调函数放到宏任务队列的队尾
- 使用异步回调,将回调放到当前宏任务的后面
第一种方式明显不合适,会导致阻塞,也无法实现延迟绑定。第二种,如果宏任务队列很长,回调迟迟不能执行会造成页面卡顿。所以 Promise 使用第三种方式,将回调放到当前宏任务的后面执行,即使用微任务。
使用微任务解决了:
- 使用异步回调代替同步解决了浪费 CPU 的问题
- 放到当前宏任务后执行,解决了回调执行的实时性问题
# 简易版Promise
写之前我们通过一个简单的例子分析下 Promise 的功能:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
})
}).then(value => {
console.log(value);
}, error => {
console.log(error);
})
/*
'ok'
*/
- Promise 可以被 new ,所以是一个构造函数
- 这个构造函数的参数是一个函数,这个函数接收两个参数,这两个参数也是函数,一个处理成功,一个处理失败
- 可以调用 then 方法,then 方法中可以传递两个函数,一个处理成功,一个处理失败
- 有三个状态:pending、resolved、rejected ,且状态一经改变不能再次被更改
const PENDING = 'pending',
REJECTED = 'rejected',
FULFILLED = 'fulfilled';
function MPromise(executor) {
const self = this;
self.value = null;
self.error = null;
self.status = PENDING; // 默认状态为 PENDING
self.onFulfilled = null; // 成功的回调
self.onRejected = null; // 失败的回调
const resolve = function(value) {
if (self.status !== PENDING) return;
setTimeout(() => {
self.status = FULFILLED;
self.value = value;
self.onFulfilled(value);
})
}
const reject = function(error) {
if (self.status !== PENDING) return;
setTimeout(() => {
self.status = REJECTED;
self.error = error;
self.onRejected(error)
})
}
executor(resolve, reject);
}
MPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === PENDING) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}else if(this.status === FULFILLED) {
onFulfilled(this.value);
}else {
onRejected(this.error);
}
return this;
}
测试:
new MPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
})
}).then(value => {
console.log(value);
}, error => {
console.log(error);
})
/*
'ok'
*/
可以看到用我们自己写的 Promise 也正确输出了理想的结果。
# 处理 then 方法不传参的情况
从 MDN 关于 Promise.prototype.then() (opens new window) 方法的介绍中可以看到,then方法接收的两个参数都是可选的。测试下我们自己的代码:
new MPromise((resolve, reject) => {
resolve('ok');
}).then()
发现如果 then 方法中什么都不传会报错,下面我们来修复下这个问题:
MPromise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => v;
if (this.status === PENDING) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}else if(this.status === FULFILLED) {
onFulfilled(this.value);
}else {
onRejected(this.error);
}
return this;
}
# 数组回调
再来看一种 Promise 的用法:
const p1 = new Promise((resolve, rejected) => {
resolve('ok')
})
const p2 = p1.then(res => {
console.log(res)
})
const p3 = p2.then(res => {
console.log(res)
})
/*
'ok'
'ok'
*/
换成我们自己的 Promise
const p1 = new MPromise((resolve, rejected) => {
resolve('ok')
})
const p2 = p1.then(res => {
console.log(res)
})
const p3 = p2.then(res => {
console.log(res)
})
/*
'ok'
*/
可以看到使用我们自己写的 Promise 只输出了一个 'ok',下面我们来解决这个问题。我们对代码做如下修改:
const PENDING = 'pending',
REJECTED = 'rejected',
FULFILLED = 'fulfilled';
function Promise(executor) {
const self = this;
self.value = null;
self.error = null;
self.status = PENDING; // 默认状态为 PENDING
self.onFulfilled = []; // 成功的回调
self.onRejected = []; // 失败的回调
const resolve = function(value) {
if (self.status !== PENDING) return;
setTimeout(() => {
self.status = FULFILLED;
self.value = value;
self.onFulfilled.forEach(onFulfilled => {
onFulfilled(value)
})
})
}
const reject = function(error) {
if (self.status !== PENDING) return;
setTimeout(() => {
self.status = REJECTED;
self.error = error;
self.onRejected.forEach(onRejected => {
onRjected(error)
})
})
}
executor(resolve, reject);
}
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => v;
if (this.status === PENDING) {
this.onFulfilled.push(onFulfilled);
this.onRejected.push(onRejected);
}else if(this.status === FULFILLED) {
onFulfilled(this.value);
}else {
onRejected(this.error);
}
return this;
}
再来测试下我们刚才的代码,发现输出了两次 'ok' ,和我们预期的结果一样。
# 优化链式调用
我们再来看 Promise 的另一种使用情况:
const p = new Promise((resolve, reject) => {
resolve('hello')
})
p.then(res => {
return res + ' world'
}).then(res => {
console.log(res)
})
/*
'hello wrold'
*/
上面这种用法就是 Promise 的链式调用。
使用我们自己写的 Promise 测试上面代码,输出的结果却是 'hello'。这是为什么呢?我们把代码做下简单的改造:
const p = new Promise((resolve, reject) => {
resolve('hello')
})
const p1 = p.then(res => {
return res + ' world'
})
const p2 = p1.then(res => {
console.log(res)
})
console.log(p1 === p2) // false
我们自己写的:
const p = new MPromise((resolve, reject) => {
resolve('hello')
})
const p1 = p.then(res => {
return res + ' world'
})
const p2 = p1.then(res => {
console.log(res)
})
console.log(p1 === p2) // true
可以看到使用系统的 Promise, p1 和 p2是不相等的,而使用我们自己的 MPromise, p1 和 p2 却是相等的。回顾下我们自己写的 MPromise, 在then 方法中,我们返回的 this 指代的是第一次生成的实例。
下面我们来完善我们的代码:
MPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => v;
if (this.status === PENDING) {
return new MPromise((resolve, reject) => {
this.onFulfilled.push(value => {
try {
const x = onFulfilled(value)
resolve(x)
}catch(err) {
reject(err)
}
});
this.onRejected.push(error => {
try {
const x = onRejected(value)
resolve(x)
}catch(err) {
reject(err)
}
});
})
} else if (this.status === FULFILLED) {
onFulfilled(this.value);
} else {
onRejected(this.error);
}
return this;
}
测试之前的代码,可以看到拿到了正确结果:
const p = new MPromise((resolve, reject) => {
resolve('hello')
})
p.then(res => {
return res + ' world'
}).then(res => {
console.log(res)
})
/*
'hello world'
*/
但是还有个问题,看下面这个代码:
new Promise((resolve, reject) => {
resolve(Promise.resolve('ok'))
}).then(res => {
console.log(`res->${res}`)
})
/*
'ok'
*/
输出结果是 'ok',换成我们自己写的 Promise 在看下
new MPromise((resolve, reject) => {
resolve(Promise.resolve('ok'))
}).then(res => {
console.log(`res->${res}`)
})
/*
'[object Promise]'
*/
这里需要注意下,在 then 方法的 resolve(x) 地方,这个 x 的值可能也是一个 promise ,直接 resolve(x) 明显不合适,我们需要处理下这种情况:
function resolvePromise(p, x, resolve, reject) {
if (x instanceof MPromise) {
if (x.status === PENDING) {
x.then(res => {
resolvePromise(p, res, resolve, reject)
}, error => {
reject(error);
})
}else {
x.then(resolve, reject)
}
}else {
// x 不是 Promise 的话直接 resolve 即可
resolve(x)
}
}
MPromise.prototype.then = function (onFulfilled, onRejected) {
let p;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => v;
if (this.status === PENDING) {
return p = new MPromise((resolve, reject) => {
this.onFulfilled.push(value => {
try {
const x = onFulfilled(value)
resolvePromise(p, x, resolve, reject)
}catch(err) {
reject(err)
}
});
this.onRejected.push(error => {
try {
const x = onRejected(value)
resolve(x)
}catch(err) {
reject(err)
}
});
})
} else if (this.status === FULFILLED) {
return p = new MPromise((resolve, reject) => {
try {
const x = onFulfilled(this.value)
resolvePromise(p, x, resolve, reject)
}catch(err) {
reject(err)
}
})
} else {
return p = new Promise((resolve, reject) => {
try {
const x = onRejected(this.error)
resolvePromise(p, x, resolve, reject)
}catch(err) {
reject(err)
}
})
}
return this;
}
# 错误捕获
MPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
# Promise.resovle
MPromise.resolve = (param) => {
if(param instanceof MPromise) return param;
return new MPromise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}
# Promise.reject
先分析下 Promise.reject:
- 返回一个 Promise
- 传入的参数也就是失败原因会往下传
MPromise.reject = function(reason) {
return new MPromise((resolve, reject) => {
reject(reason)
})
}
# Promise.finally
MPromise.prototype.finally = function(callback) {
this.then(value => {
return MPromise.resolve(callback()).then(() => {
return value;
})
}, error => {
return MPromise.resolve(callback()).then(() => {
throw error;
})
})
}
# Promise.all
首先通过 Promise.all 的基本使用来分析下 Promise.all:
- 它的参数是一个数组,数组中的值不一定是 Promise 对象
- 如果参数的数组是一个空数组,则直接resolve这个空数组
- 有一个失败,就失败,但是其他的也会执行
- 成功的结果是一个数组
- 返回的是一个 Promise 对象
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
const len = promises.length, resArr = [];
if (len === 0) {
resolve(promises)
return;
}
for (let i = 0; i < len; i++) {
Promise.resolve(promises[i]).then(res => {
resArr[i] = res;
if (i === len - 1) resolve(resArr)
}).catch(err => {
reject(err);
})
}
})
}
# Promise.race
const p = Promise.race([1, 2, 3]).then(res => {
console.log(res) // 1
})
console.log(p) // Promise {<pending>}
/*
Promise {<pending>}
1
*/
从 Promise.race 的基本使用分析可以看出:
- 参数是一个数组,数组的值不一定是 promise 对象
- 有一个执行成功就结束,结果是这个成功的 promise 值
- 有一个失败就结束,失败原因就是这个失败的 promise 的失败的原因
- 返回值是一个 promise 对象
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
const len = promises.length;
if (len === 0) return
for (let i = 0; i < len; i++) {
Promise.resolve(promises[i]).then(res => {
resolve(res)
return;
}).catch(err => {
reject(err);
return;
})
}
})
}
# Generator
Generator 在我们实际开发中使用的非常少,详细使用请阅读阮一峰老师的文章:
- Iterator 和 for...of 循环 (opens new window)
- Generator 函数的语法 (opens new window)
- Generator 函数的异步应用 (opens new window)
这里只做简单介绍,先来看一个例子:
function * fn() {
yield 1
yield (function() {return 2})()
yield 3
}
const f = fn()
console.log(Object.prototype.toString.call(f))
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
/*
'[object Generator]'
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: undefined, done: true}
*/
从上面的代码可以看出,使用 function* 来声明一个函数,调用这个函数返回的是一个 Generator 对象,碰到 yield 语句代码暂停执行,使用返回的这个对象调用 next() 方法会执行函数中的代码。
上面介绍了 Generator 最简单的用法。
Generator 返回的是一个 Iterator 对象,具有 [Symbol.iterator] 属性的对象可以生成一个 Iterator 对象。
const arr = [1, 2]
const iterator = arr[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
/*
{value: 1, done: false}
{value: 2, done: false}
{value: 1, done: false}
{value: undefined, done: true}
*/
可以看到 Iterator 对象可以通过 next 访问访问每个元素的值。
我们还可以通过 for...of 访问
const arr = [1, 2]
const iterator = arr[Symbol.iterator]()
for (let item of iterator) {
console.log(item)
}
/*
1
2
*/
既然我们前面说了 Generator 返回的也是一个 Iterator 对象,我们来看下之前的 Generator 代码:
function * fn() {
yield 1
yield (function() {return 2})()
yield 3
}
const f = fn()
// 可以看到返回的这个 f 对象具有 Symbol.iterator 属性
console.log(f[Symbol.iterator]) // [Symbol.iterator]() { [native code] }
// 可以使用 for...of 访问
for (let item of f) {
console.log(item)
}
// 可以通过 next 访问
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
/*
[Symbol.iterator]() { [native code] }
1
2
3
{value: undefined, done: true}
{value: undefined, done: true}
{value: undefined, done: true}
{value: undefined, done: true}
*/
# thunk 函数
上面我们花费了大量篇幅介绍 Generator ,但似乎都没到重点,这篇文章整体介绍的是 JS 的异步解决方案,怎么用 Generator 来处理异步呢?
在开始步入正题前,我们需要先说下 thunk 函数,这对我们实现异步很重要。
// 模拟 ajax 请求
function ajax(param, cb) {
setTimeout(() => {
cb('ajax请求结果:巴拉巴拉')
}, 1000)
}
ajax(1, (res) => {
console.log(res)
})
上面的 ajax 函数是一个异步函数,我们对它进行一些改造:
const thunk = function(param) {
return function (cb) {
ajax(param, cb)
}
}
const ajaxThunk = thunk('1')
ajaxThunk((data) => {
console.log(data)
})
通过上面的例子我们看到 thunk 函数返回的是一个函数,ajaxThunk 这个函数只接受一个参数且这个参数是一个函数。
总结下: 我们通过对传统的异步函数进行封装,得到的一个只有一个参数且这个参数是一个callback函数的函数就是 thunk 函数。
let res = 0
function ajax(param, cb) {
res++
setTimeout(() => {
cb(`ajax请求结果${res}`)
}, 1000)
}
const thunk = function (param) {
return function (cb) {
ajax(param, cb)
}
}
function* gen() {
const r1 = yield thunk(1)
console.log(`r1 -> ${r1}`)
const r2 = yield thunk(2)
console.log(`r2 -> ${r2}`)
}
const g = gen()
// g.next().value 是一个 thunk 函数,res1 是第一次调用请求方法返回的结果
g.next().value((res1) => {
// g.next(res1) 将第一次请求的结果传给 r1
// res2 是第二次请求返回的结果
g.next(res1).value((res2) => {
// 将第二次请求结果传给 r2
g.next(res2)
})
})
/*
r1 -> ajax请求结果1
r2 -> ajax请求结果2
*/
上面我们实现了一个模拟 ajax 请求的例子,但向上面那样调用太麻烦了,如果每次都向上面那样调用我宁愿使用回调函数。。。
下面我们来封装一个 run 方法来简化我们的代码:
// 模拟返回结果
let res = 0
// 模拟 ajax 请求
function ajax(param, cb) {
res++
setTimeout(() => {
cb(`ajax请求结果${res}`)
}, 1000)
}
const thunk = function (param) {
return function (cb) {
ajax(param, cb)
}
}
function* gen() {
const r1 = yield thunk(1)
console.log(`r1 -> ${r1}`)
const r2 = yield thunk(2)
console.log(`r2 -> ${r2}`)
}
function run(generator) {
const g = generator()
function next(data) {
const res = g.next(data)
if (res.done) return
res.value(next)
}
next()
}
run(gen)
/*
r1 -> ajax请求结果1
r2 -> ajax请求结果2
*/
# co 库
虽然通过上面的封装代码简单了很多,每次都要自己写个 run 方法也比较麻烦,我们可以使用 co 库。上面的例子中 yield 后面跟的是一个 thunk 函数,这里我们换成一个 promise 试试。
// 引入 co 库
// <script src="https://cdn.bootcdn.net/ajax/libs/co/4.1.0/index.js"></script>
// 模拟 ajax 请求
function ajax(param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(param)
}, 1000)
})
}
co(function* fn() {
const param = 1
const r1 = yield ajax(param)
const r2 = yield ajax(r1 + 2)
const r3 = yield ajax(r2 + 3)
console.log(r3)
})
/*
3
*/
# async和await
使用 Generator 来处理异步还是比较麻烦,ES8规范新增了 async/await ,可以让我们以同步的方式写异步代码。
我们先使用 async 和 await 改写下上面的代码,后续再介绍 async 和 await。
function ajax(data, success) {
return new Promise((resolve, reject) => {
$.ajax({
url: "/api/getWeather",
data: {
zipcode: data
},
success: resolve
});
})
}
async function fecth() {
const a = await ajax('xx')
const b = await ajax(a)
const c = await ajax(b)
const d = await ajax(c)
}
fecth().catch(err => {
console.log(err)
})
可以看到,改写后的代码无论从阅读性还是从维护性都很好。
async、await可以看做是 Generator 的语法糖。
# async
async 关键字用于声明异步函数,使用 async 声明的函数具有异步特征,但代码总体是同步求值的。
console.log(1)
async function fn() {
console.log(2)
}
fn()
console.log(3)
/*
1
2
3
*/
异步函数的返回值会被 Promise.resolve() 包装成一个期约对象。
async function fn() {
return 1
}
fn().then(res=>console.log(res)) // 1
异步函数抛出错误会返回拒绝的期约。
async function fn() {
throw new Error('error')
}
fn().catch(err => console.log(err)) // Error: error
拒绝期约的错误不会被异步函数捕获。
async function fn() {
Promise.reject('err')
}
fn().catch(err => console.log(err)) // Uncaught (in promise) err
async 是 Generator 函数的语法糖,相比 Generator 来说,具有以下特点:
- 内置执行器。async 函数自带执行器,调用方式和普通函数一样。
- 更好的语义。相比 * 和 yield 更语义化。
- 更广的适用性。yield 函数后面只能是 Thunk 函数或者 Promise 对象,而 async 函数中的 await 后面可以是任何值。
- 返回值是 Promise,可以直接调用 then 方法,比 Generator 返回的 Iterator 对象方便。
console.log(1)
async function fn() {
return 2
}
console.log(3)
/*
*/
# await
await 关键字必须在 async 函数中直接使用,await 关键字可用于暂停异步函数代码的执行。
async function fn() {
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
console.log(await p);
}
fn();
// 3
await 关键字会暂停执行异步函数后面的代码,让出 JS 执行线程。JavaScript运行时在碰到await关键字时,会记录在哪里暂停执行。等到await右边的值可用了,JavaScript运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。即使await后面跟着一个立即可用的值,函数的其余部分也会被异步求值
console.log(1)
async function fn() {
const a = await 2
console.log(a)
}
fn();
console.log(3)
/*
1
3
2
*/
await 关键会尝试解包对象的值,将这个值赋值给表达式。
async function fn() {
console.log(await Promise.resolve('hello'));
}
fn(); // 'hello'
单独的Promise.reject()不会被异步函数捕获,而会抛出未捕获错误,对拒绝的期约使用await则会释放(unwrap)错误值(将拒绝期约返回)。
async function fn() {
console.log(1);
await Promise.reject(3);
console.log(4); // 这行代码不会执行
}
// 给返回的期约添加一个拒绝处理程序
fn().catch(console.log);
console.log(2);
// 1
// 2
// 3
异步函数中如果不包含await,其执行和普通函数一样。
console.log(1)
async function fn() {
console.log(2)
}
fn();
console.log(3)
/*
1
2
3
*/
如果await后面是一个期约,老版浏览器会有两个任务被添加到消息队列并被异步求值。新版浏览器只生成一个异步任务。
async function foo() {
console.log(await Promise.resolve('foo'));
}
async function bar() {
console.log(await 'bar');
}
async function baz() {
console.log('baz');
}
foo();
bar();
baz();
/*
新版浏览器:
baz
foo
bar
旧版浏览器:
baz
bar
foo
*/
参考:
《JS 高级程序设计(第四版)》
深入理解 JavaScript 异步 (opens new window)