之前搞了一个拖了很长时间都没搞好的项目,bug 也多,后来迫于无奈就完全推倒重写了。这次重写完全就是怎么开心怎么来,以前那些看过但没去接触、用过的 class
,canvas
画布、[...list]
语法糖等等,这次全拿来当主力了,然后着重面向对象编程的概念,最后突然发现这个觉得很难搞的项目原来弄起来挺轻松。
由此而来想法就是学习的东西不能只看了不用呀,不能固步自封。
1 Promise
对象
参考文章《Promise 对象》
Promise
简单说就是一个 容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的 API,各种 异步 操作都可以用同样的方法进行处理。
Promise
有以下特点:
- 对象状态不受外界的影响,
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有 异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
下面代码创造一个 promise
对象:
const promise = new Promise((relsove, reject) => {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve
和 reject
。
resolve
函数的作用是,将 Promise
对象的状态从 pending
变为 resolved
,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject
函数的作用是,将 Promise
对象的状态从 pending
变为 rejected
,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去(即主动调用 reject
函数或捕获异常导致状态从 pending
变为 rejected
)。
1.1 then
方法
Promise
实例生成以后,可以用 then
方法分别指定 resolved
状态和 rejected
状态的回调函数,then
方法接受两个回调函数作为参数,第一个回调函数是 Promise
对象的状态变为 resolved
时调用,第二个回调函数是 Promise
对象的状态变为 rejected
时调用,同时 then
方法返回一个新的 Promise
对象,所以可以采用链式的方法调用 Promise
对象如:
promise.then(function(value) {
// success
}, function(error) {
// failure
});
下面是一个简单的例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
重点需要注意的是 Promise
对象一旦创建就会立刻执行,如:
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
上面依次输出的内容是:
Promise
Hi!
resolved.
1.2 catch
方法
catch
方法其实就是 then(null, rejection)
的别名,另一种写法,如:
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
一般来说,不要在
then
方法里面定义Reject
状态的回调函数(即then
的第二个参数),总是使用catch
方法。
如果 Promise
在 resolve
语句后面,此时抛出错误是无效的。因为 Promise
的状态一旦改变,就永久保持该状态,不会再变了,如:
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
Promise
对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
1.3 finally
方法
finally
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作。finally
方法的回调函数不接受任何参数
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
2 async
和 await
参考文章 《async 函数》
async
用于修饰函数,它表示函数里有异步操作,而 await
表示紧跟在后面的表达式需要等待结果。
正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值。
下面代码表示一个计时器:
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}
// 用法
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000).then(() => console.log("1 秒过去"));
}
}
one2FiveInAsync();
async
函数返回的 Promise
对象,但它的返回必须等到内部所有 await
命令后面的 Promise
对象执行完,才会发生状态改变,除非遇到 return
语句或者抛出错误。也就是说,只有 async
函数内部的异步操作执行完,才会执行 then
方法指定的回调函数。
而任何一个 await
语句后面的 Promise
对象变为 reject
状态,那么整个 async
函数都会中断执行。如:
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。此时可以提前捕获错误,如:
async function f() {
await Promise.reject('出错了').catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f().then(v => console.log(v));
// 出错了
// hello world
也可以使用 try...catch
的形式,如:
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
}
return await('hello world');
}