因为前端都是自己研究,实践不多,所以一直以来对promise和async的认识都停留在表层,最近对这两个概念进行了深入的研究,现在我们就来聊聊Promise和Async。
一.promise
先来说说我之前对promise的理解
promise是一个对象,但并不是一个普通的对象,整个对象中封装了一个耗时的任务,这个对象有三种状态pending(进行中),resolved(任务完成),rejected(任务失败),resolved和rejected都可以传入参数,参数会传递给then中的函数。同时在promise对象的后面跟着then(),then中可以放两个回调函数,当promise对象状态变为resolved就调用then中的第一个方法,状态变为rejected就调用第二个方法。
好吧,就上面这些了,具体对于promise如何实现的,promise还有没有其他方法一概不知。。
今天深入学习后总结的几个点:
1. 当向resolved中传入另一个promise对象时
注意,这时p1
的状态就会传递给p2
,也就是说,p1
的状态决定了p2
的状态。如果p1
的状态是Pending
,那么p2
的回调函数就会等待p1
的状态改变;如果p1
的状态已经是Resolved
或者Rejected
,那么p2
的回调函数将会立刻执行。
2.Promise.prototype.catch()
catch方法可用可不用,当使用catch时,then中定义的第二个回调函数就要写到catch中,状态就会变为Rejected时
,就会调用catch
方法指定的回调函数。同时,then
方法指定的回调函数,如果运行中抛出错误,也会被catch
方法捕获。Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
3.Promise.all()
Promise.all
方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all
方法接受一个数组作为参数,p1
、p2
、p3
都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为Promise实例,再进一步处理。(Promise.all
方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成Resolved
,p
的状态才会变成Resolved
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
4.Promise.race()
Promise.race
方法同样是将多个Promise实例,包装成一个新的Promise实例。与all方法区别在于只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
5.Promise.resolve()
Promise.resolve方法用于将一个对象转化为promise对象
6.done()
Promise对象的回调链,不管以then
方法或catch
方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done
方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
7.finally()
finally
方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done
方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
下面是一个例子,服务器使用Promise处理请求,然后使用finally
方法关掉服务器。
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
8.Promise.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f
是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f
是否包含异步操作,都用then
方法指定下一步流程,用catch
方法处理f
抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
上面的写法有一个缺点,就是如果f
是同步函数,那么它会在下一轮事件循环执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面代码中,函数f
是同步的,但是用 Promise 包装了以后,就变成异步执行了。
二.async
我对于async的理解就是,他是对promise的又一次封装,当有多个promise需要按顺序执行时,我们不需要再promise1
.then(promise2).then(promise3)。。。了,可以写成下面这样:
async function main() {
await promise1;
await promise2;
await promise3;
}
理解起来应该算是十分的简单了,就是按顺序执行这三个promise对象。
async就像一个更大的promise,它也有then和catch方法,它内部可以有一个return语句返回值,返回的是一个promise对象,返回值会变成then的参数。如果await
后面的异步操作出错,那么等同于async
函数返回的Promise对象被reject,
错误信息会被catch接收。为了避免因为报错停止执行,我们可以将await
放在try...catch
结构里面,这样接下来的await
就会继续执行。
下面来说说async的底层实现
async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function*() {
// ...
});
}
所有的async
函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。
下面给出spawn
函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var 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 不是单线程执行的吗,开这么多协程难道可以一起执行吗?
答案是:并不能。一个线程一次只能执行一个协程。比如当前执行 A 协程,另外还有一个 B 协程,如果想要执行 B 的任务,就必须在 A 协程中将JS 线程的控制权转交给 B协程
,那么现在 B 执行,A 就相当于处于暂停的状态。
举个具体的例子:
function* A() {
console.log("我是A");
yield B(); // A停住,在这里转交线程执行权给B
console.log("结束了");
}
function B() {
console.log("我是B");
return 100;// 返回,并且将线程执行权还给A
}
let gen = A();
gen.next();
gen.next();
// 我是A
// 我是B
// 结束了