因为前端都是自己研究,实践不多,所以一直以来对promise和async的认识都停留在表层,最近对这两个概念进行了深入的研究,现在我们就来聊聊Promise和Async。
先来说说我之前对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的理解就是,他是对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 // 结束了