最近在写【重拾前端】系列,下面有几个快速通道,大家自取
前言
老规矩,还是先了解一下什么是异步。异步其实是一个相对比较高级的一个概念。
通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.
如果说一个事情需要等待上一件事情做完才能做,但是他们之前又没有强耦合关系。这样就在那里干等就毫无意义。特别是现在计算机普遍都有多核CPU的时代。
举一个形象的🌰。
我们现在要去吃海底捞(海底捞打钱!),我们发现要排队,需要排2个小时左右,这个时候我们要干嘛?难道是干等吗?肯定不是!那样蠢蠢的硬等很呆,我们可能会拿出手机刷刷朋友圈,或者玩一把紧张刺激的王者荣耀又或者去看一场电影等等....在我们做这些事情的时候我们的排队并没有被耽误,甚至可以和服务员说快到了打个电话给我(这个是常见的callback方法,待会讨论)
这样你可以同时完成其他工作,这就是异步编程的出发点。
本文总结了几个异步的方案,理解它们可以让你写出结构更合理、性能更出色、维护更方便的Javascript程序。
异步callback
上来就是一个英俊的例子
var response = fetch('myImage.png');
var blob = response.blob();
没有人可以完美预测到每种网络情况下这个response,是否可以完美获得fetch获得的内容,很有可能会报错。
这个时候我们的callback就✨bu ling bu ling的登场啦。
有些小伙伴可能觉得callback似乎和自己没有太大关系。。。emmmm....
btn.addEventListener('click', () => {
alert('You clicked me!');
});
这样呢?有没有感觉有点儿熟悉了,其他这个就相当于react中的
<div onclick={() =>{alert('You clicked me!');}}>
点我
</div>
是不是很熟悉,这个其实就是最常见的callback(回调函数)。
又或者比较常见的
setTimeout(() => {
alert('You clicked me!');
},2000)
很常见,大致意思就是2秒钟之后会回调一下这个匿名函数,虽然不一定会准确执行,这个涉及到事件循环,感兴趣的可以去找一下我前阵子写的事件循环😊。
发布-订阅模式
发布-订阅模式:又称为观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖于它的对象都将得到通知。
在写React的时候,有一个非常非常非常常见的🌰:
import React, {useState, useEffect} from 'react';
const test = () => {
const [derrick, setDerrick] = useState(0)
useEffect(()=> {
console.log(derrick)
}, [derrick])
return(
<button onClick={() => {setDerrick(derrick+1)}}>点我</button>
)
}
useEffect订阅了derrick这个变量的变化,然后监听到变化之后就会进行一次log,打印出derrick变量的值。
Promise
回调救星
之前介绍过回调函数这种异步编程。
加入我希望在A执行完之后执行B,然后执行C,然后执行D....最后执行Z
写出来的代码可能是这样的
var sayhello = function (name, callback) {
setTimeout(function () {
console.log(name);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
像本例一样嵌套多个方法调用会创建错综复杂的代码,会难以理解与调试。当想要实现更复 杂的功能时,回调函数也会存在问题:若想让两个异步操作并行运行,并且在它们都结束后 提醒你,那该怎么做?若想同时启动两个异步操作,但只采用首个结束的结果,那又该怎么 做?
在这些情况下,你需要追踪多个回调函数并做清理操作, 不过好在promise横空出世拯救了世界。
涉及面试题
promise用过吗?请你介绍一下promise。
其实这里很多同学可能就不知道怎么介绍了,可能只停留在怎么用。我们可以从几个方面来介绍。
- promise是什么?
- promise解决了什么问题
- promise的优缺点
promise是什么?
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
promise解决了什么问题?
- 解决可读性的问题
- 解决信任问题
可读性
可读性,之前的回调地狱promise就可以优雅的解决。(还有更加优雅的,待会会介绍。)
var sayhello = function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name);
resolve(); //在异步操作执行完后执行 resolve() 函数
}, 10000);
});
}
sayhello("first").then(function () {
return sayhello("second"); //仍然返回一个 Promise 对象
}).then(function () {
return sayhello("third");
}).then(function () {
console.log('end');
}).catch(function (err) {
console.log(err);
})
信任问题
这个问题可以用promise的几个特点来解释:
(1)对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
由于Promise只能被决议一次,且决议之后无法改变,所以,即便是多次回调,也不会影响结果,决议之后的调用都会被忽略。
//信任问题演示
//回调
function method(cb){
setTimeout(function(){
cb && cb();
//因为某些bug导致某个函数多执行了一次
cb && cb();
},1000);
}
//promise
function method2(){
return new Promise(resolve=>{
setTimeout(function(){
resolve();
//resolve成功调用一次之后,后面的不会再执行
resolve();
},1000);
})
}
控制反转。体现在对于函数的把控,比如this等。
//回调
function method(cb){
setTimeout(function(){
cb && cb.call({a:1,b:2});//执行回调,但是添油加醋
},1000);
}
//promise
function method2(){
return new Promise(resolve=>{
setTimeout(function(){
resolve();//调用的resolve都是自己写的,改善了控制反转的问题
},1000);
})
}
手撕A+规范的promise
面试题:你知道promise的A+规范吗?(本人在浩鲸科技被问到的,当时答的支支吾吾。。。)
我何止是知道,我甚至可以手写一个符合A+规范的promise!
A+规范快速入口,点我
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
}
}
executor(resolve, reject);
}
promise里面最难的就是then了,我们必须将这个方法写在他的原型链上面,并且他接受两个参数,一个是成功的回调,一个是失败的回调。
废话不多说,开冲!
支持异步
在此之前,我们需要加两个变量。
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
}
}
executor(resolve, reject);
}
颜值高又细心的同学会发现,我多加了两个变量。
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
这两个变量呢,也非常好理解,就是存储回调的。那么有一个非常非常重要的问题,我写的时候也困扰了好久,为什么一定要是一个数组呢?我存储为一个函数不行吗?到时候执行就行啦。。。
qs,和我想一样的帅哥靓女可能比较多。这里我统一回复:
假如说。以下的情况出现的话,咋办。
var p = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(4)
}, 1000)
})
p.then((res)=>{
//4 res
console.log(res, 'res')
})
p.then((res1)=>{
//4 res1
console.log(res1, 'res1')
})
这种情况出现,如果我们只是一个函数,那么我们的console.log(res, 'res')
这辈子都无法问世,为什么?因为被后门的console.log(res1, 'res')
给覆盖了呀。对不对,如果说这个resolve
他延迟执行,这个时候我们需要做的是把then
函数给保存下来,等到resolve
被执行的时候,依次执行我们所有创建的then
里面的回调函数。
听懂掌声!
没听懂的接着往下看,我们把剩余工作做完,然后举一个例子。马上就能明白了。
刚刚说到,我们补充两个变量,然后写下我们的then
函数。
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'fulfilled') {
onFulfilled(self.value);
}
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'rejected') {
onRejected(self.reason);
}
// 如果是pending应该存储当前的回调函数
if(self.status === 'pending') {
self.onResolved.push(() => {onFulfilled(self.value)})
self.onRejected.push(() => {onRejected(self.reason)})
}
}
然后我们还需要在resolve
或者reject
的时候执行我们存储起来的这些函数。
完整代码如下:
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
// 执行我们存储的函数;
self.onResolved.forEach(fn => fn())
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
// 执行我们存储的函数;
self.onRejected.forEach(fn => fn())
}
}
executor(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'fulfilled') {
onFulfilled(self.value);
}
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'rejected') {
onRejected(self.reason);
}
// 如果是pending应该存储当前的回调函数
if(self.status === 'pending') {
self.onResolved.push(() => {onFulfilled(self.value)})
self.onRejected.push(() => {onRejected(self.reason)})
}
}
OK。我们先试试我们自己写的promise。跑一下demo吧。为了给懒同学便捷的调试,直接CV下面就好啦。
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
// 执行我们存储的函数;
self.onResolved.forEach(fn => fn())
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
// 执行我们存储的函数;
self.onRejected.forEach(fn => fn())
}
}
executor(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'fulfilled') {
onFulfilled(self.value);
}
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'rejected') {
onRejected(self.reason);
}
// 如果是pending应该存储当前的回调函数
if(self.status === 'pending') {
self.onResolved.push(() => {
onFulfilled(self.value)
})
self.onRejected.push(() => {
onRejected(self.reason)
})
}
}
var p = new MyPromise((resolve, reject)=>{
setTimeout(()=>{
resolve(4)
}, 1000)
})
p.then((res)=>{
//4 res
console.log(res, 'res')
})
p.then((res1)=>{
//4 res1
console.log(res1, 'res1')
})
嗯~如愿输出了,那回到刚刚的问题。我们把回调的地方不要用数组而用函数呢?不用同学们打字了,直接CV👇的代码就好了。
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
// 执行我们存储的函数;
self.onResolved.forEach(fn => fn())
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
// 执行我们存储的函数;
self.onRejected.forEach(fn => fn())
}
}
executor(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'fulfilled') {
onFulfilled(self.value);
}
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'rejected') {
onRejected(self.reason);
}
// 如果是pending应该存储当前的回调函数
if(self.status === 'pending') {
self.onResolved.push(() => {
onFulfilled(self.value)
})
self.onRejected.push(() => {
onRejected(self.reason)
})
}
}
var p = new MyPromise((resolve, reject)=>{
setTimeout(()=>{
resolve(4)
}, 1000)
})
p.then((res)=>{
//4 res
console.log(res, 'res')
})
p.then((res1)=>{
//4 res1
console.log(res1, 'res1')
})
是不是只有console.log(res1, 'res1')
啦?因为执行then
的时候,我们的resolve还有没有被调用,如果我们有多个then
的话,我们需要把他们存储起来,然后依次调用。所以我们需要用到数组,当然也可以是别的方法,核心思想就是把他们存起来,在需要调用的时候调用。
是不是很好理解?!!
听懂掌声!
而且A+也要求了。
2.2.6:then
may be called multiple times on the same promise.(promise 的
then
可以链式调用多次)2.2.6.1:If/when
promise
is fulfilled, all respectiveonFulfilled
callbacks must execute in the order of their originating calls tothen
.(如果或当 promise 转态是 fulfilled 时,所有的 onFulfilled 回调回以他们注册时的顺序依次执行)
2.2.6.2:If/when
promise
is rejected, all respectiveonRejected
callbacks must execute in the order of their originating calls tothen
.(如果或当 promise 转态是 rejected 时,所有的 onRejected 回调回以他们注册时的顺序依次执行)
我们对promise的理解更进一步了。记得把代码改回去,继续往下走。
刚刚我们的demo还不够鲁棒,而且我们的测试用例都是完美情况下。
接下来要剑走偏锋了。
A+:
2.2.1:Both
onFulfilled
andonRejected
are optional arguments:(onFulfilled 和 onRejected 都是可选参数:)
2.2.1.1 If
onFulfilled
is not a function, it must be ignored.(如果 onFulfilled 不是函数,它会被忽略)
2.2.1.2 If
onRejected
is not a function, it must be ignored.(如果 onRejected 不是函数,它会被忽略)
2.2.7.2: If either
onFulfilled
oronRejected
throws an exceptione
,promise2
must be rejected withe
as the reason.(如果 onFulfilled 或 onRejected 里抛出了一个异常,那么 promise2 必须捕获这个错误(接受一个 reason 参数))
所以我们首先要做的就是,把错误给提前捕获,然后丢给reject
emmm....接下我贴代码都是一起贴了,因为我怕有同学会掉队,里面也会有相应的注释帮助会走神的同学跟紧这辆五菱宏光。
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
// 执行我们存储的函数;
self.onResolved.forEach(fn => fn())
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
// 执行我们存储的函数;
self.onRejected.forEach(fn => fn())
}
}
// 捕获错误传给reject。法律规定!
try{
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
// 如果不是函数就忽略 2.2.1.1
if(self.status === 'fulfilled') {
typeof onFulfilled === 'function' && onFulfilled(self.value);
}
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'rejected') {
typeof onFulfilled === 'function' && onRejected(self.reason);
}
// 如果是pending应该存储当前的回调函数
// 如果不是函数就忽略 2.2.1.2
if(self.status === 'pending') {
// 如果不是函数就忽略 2.2.1.1
typeof onFulfilled === 'function' && self.onResolved.push(() => {
onFulfilled(self.value)
})
// 如果不是函数就忽略 2.2.1.2
typeof onFulfilled === 'function' && self.onRejected.push(() => {
onRejected(self.reason)
})
}
}
链式调用then
开始准备进入promise的难点以及核心点:链式调用。
2.2.7:
then
must return a promise(then 方法一定返回一个 promise)
我们需要返回新的promise的话,可以简单实现,快速迭代。
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject)=>{
})
return promise2
}
2.2.7.1:If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
.(如果 onFulfilled 或 onRejected 返回的是一个 x,那么它会以[[Resolve]](promise2, x)` 处理解析)
2.2.7.2:If either
onFulfilled
oronRejected
throws an exceptione
,promise2
must be rejected withe
as the reason.(如果 onFulfilled 或 onRejected 里抛出了一个异常,那么 promise2 必须捕获这个错误(接受一个 reason 参数))
2.2.7.3:If
onFulfilled
is not a function andpromise1
is fulfilled,promise2
must be fulfilled with the same value aspromise1
.(如果 onFulfilled 不是一个函数,并且 promise1 状态是 fulfilled,那么 promise2 一定会接受到与 promse1 一样的值 value)
2.2.7.4:If
onRejected
is not a function andpromise1
is rejected,promise2
must be rejected with the same reason aspromise1
.(如果 onRejected 不是一个函数,并且 promise1 状态是 rejected,promise2 一定会接受到与 promise1 一样的值 reason)
我们需要将返回内容promise化,需要对onFulfilled和onRejected进行错误处理,这个我们之前就处理过了,如果onFulfilled和onRejected没有传参就继续传递,原生🌰来:
var p = new Promise(function(resolve, reject){
setTimeout(function(){
resolve(3)
}, 1000)
});
p.then(1,1)
.then('','')
.then()
.then(function(res){
//3
console.log(res)
})
这里不管onFulfilled和onRejected传什么值,只要不是函数,就继续向下传入,直到有函数进行接收;要做的事情太多了,不当当是判断函数这种简单的判断了,所以我们要抽离一个新的函数来复用统一处理。因此我们进行如下完善:
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
// 存储成功的回调
self.onResolved = [];
// 存储失败的回调
self.onRejected = [];
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
// 执行我们存储的函数;
self.onResolved.forEach(fn => fn())
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status = 'rejected';
// 执行我们存储的函数;
self.onRejected.forEach(fn => fn())
}
}
// 捕获错误传给reject。法律规定!
try{
executor(resolve, reject);
} catch(e) {
reject(e);
}
}
function resolvePromise(promise2, res, resolve, reject) {
// 如果promise2和结果相同,就自己无限循环调用自己的then了,死亡循环。
// 举个🌰:var a = function(){return a},这个then又会自己无限触发。所以就玩完了。
if(promise2 === res) {
return reject(new TypeError('循环引用'))
}
if(
res !== null
&& (typeof res === 'object') || typeof res === 'function')
) {
try {
// 保存是否已经调用过函数。
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.(如果 resolvePromise 和 rejectPromise 已经被调用或以相同的参数多次调用的话吗,优先第一次的调用,并且之后的调用全部被忽略(避免多次调用))
let used = false;
// 2.3.3.1:Let then be x.then. [3.5](把 x.then 赋值给 then 变量)
// 可以提防有人恶搞。像👇
// Object.defineProperty(Promise, 'then', {
// get: function(){
// throw Error('error')
// }
// })
// 这样的话我们取值就会报错。
let then = res.then;
// 如果then是函数,就默认是promise了
if(typeof then === 'function') {
then.call(
res,
value => {
if(used) {
return;
}
used = true;
// 递归解析,直到不是一个promise为止
resolvePromise(promise2, value, resolve, reject);
},
reason => {
if(used) {
return;
}
used = true;
reject(reason);
}
)
}
} catch (e) {
if(used) {
return;
}
used = true;
reject(e);
}
} else {
// 如果不是函数或者对象也不为空就直接继续传递这个值。
resolve(res);
}
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
let self = this;
// 只有promise才可以持续的链式调用then,所以这里弄了一个新的promise
let promise2 = new MyPromise((resolve, reject) => {
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'fulfilled') {
let res = onFulfilled(self.value);
resolvePromise(promise2, res, resolve, reject);
}
// 当前的状态(fulfilled/rejected/pending),默认值为pending
if(self.status === 'rejected') {
let res = onRejected(self.reason);
resolvePromise(promise2, res, resolve, reject);
}
// 如果是pending应该存储当前的回调函数
if(self.status === 'pending') {
self.onResolved.push(() => {
let res = onFulfilled(self.value);
resolvePromise(promise2, res, resolve, reject);
})
self.onRejected.push(() => {
let res = onRejected(self.reason);
resolvePromise(promise2, res, resolve, reject);
})
}
})
}
以上就是符合 Promise/A+ 规范的实现了,如果你对于这部分代码尚有疑问,欢迎在评论中与我互动。
Generator
面试题:介绍一下generator吧。
很多小伙伴可能和我一样,只是会用,但是你硬要我介绍,那我咋搞?generator的拼写是g e n e r a t o r?
“哔………………………………”
其实我重温了一下阮老师的es6课程,发现可以从以下几个方面来介绍。
- 形式上
- generator和普通的函数不同点在于,对一个
*
号,函数内部使用了yield表达式
- generator和普通的函数不同点在于,对一个
- 语法上
- Generator函数封装了多个内部状态(通过yield表达式定义内部状态)。执行Generator函数时会返回一个遍历器对象(Iterator对象)。也就是说,Generator是遍历器对象生成函数,函数内部封装了多个状态。通过返回的Iterator对象,可以依次遍历(调用next方法)Generator函数的每个内部状态。
- 调用上
- 普通函数在调用之后会立即执行,而Generator函数调用之后不会立即执行,而是会返回遍历器对象(Iterator对象)。通过Iterator对象的next方法来遍历内部yield表达式定义的每一个状态。
其实我觉得,如果能把下面的这个案例弄清楚,generator就算过关了。
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
- 首先
Generator
函数调用和普通函数不同,它会返回一个迭代器 - 当执行第一次
next
时,传参会被忽略,并且函数暂停在yield (x + 1)
处,所以返回5 + 1 = 6
- 当执行第二次
next
时,传入的参数等于上一个yield
的返回值,如果你不传参,yield
永远返回undefined
。此时let y = 2 * 12
,所以第二个yield
等于2 * 12 / 3 = 8
- 当执行第三次
next
时,传入的参数会传递给z
,所以z = 13, x = 5, y = 24
,相加等于42
async/await
什么是async/await?
其实就一句话:async 函数就是 Generator 函数的语法糖。
generator:
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
Async:
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
async对generator进行了以下三点的改进:
- 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
- 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
一个函数如果加上了async,那么就会默认返回一个promise
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function*() {
// ...
});
}
注意
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}
await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}