【重识前端】暴走的异步编程

414 阅读21分钟

最近在写【重拾前端】系列,下面有几个快速通道,大家自取

【重识前端】原型/原型链和继承

【重识前端】闭包与模块

【重识前端】全面攻破this

【重识前端】一次搞定JavaScript的执行机制

【重识前端】什么是BFC、IFC、GFC 和 FFC

【重识前端】深入内存世界

【重识前端】暴走的异步编程

【重识前端】redux源码阅读

前言

老规矩,还是先了解一下什么是异步。异步其实是一个相对比较高级的一个概念。

通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.

如果说一个事情需要等待上一件事情做完才能做,但是他们之前又没有强耦合关系。这样就在那里干等就毫无意义。特别是现在计算机普遍都有多核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解决了什么问题?

  1. 解决可读性的问题
  2. 解决信任问题
可读性

可读性,之前的回调地狱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 respective onFulfilled callbacks must execute in the order of their originating calls to then.

(如果或当 promise 转态是 fulfilled 时,所有的 onFulfilled 回调回以他们注册时的顺序依次执行)

2.2.6.2:If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.

(如果或当 promise 转态是 rejected 时,所有的 onRejected 回调回以他们注册时的顺序依次执行)

我们对promise的理解更进一步了。记得把代码改回去,继续往下走。

刚刚我们的demo还不够鲁棒,而且我们的测试用例都是完美情况下。

接下来要剑走偏锋了。

A+:

2.2.1:Both onFulfilled and onRejected 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 or onRejected throws an exception e, promise2 must be rejected with e 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 or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

(如果 onFulfilled 或 onRejected 返回的是一个 x,那么它会以[[Resolve]](promise2, x)` 处理解析)

2.2.7.2:If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.

(如果 onFulfilled 或 onRejected 里抛出了一个异常,那么 promise2 必须捕获这个错误(接受一个 reason 参数))

2.2.7.3:If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.

(如果 onFulfilled 不是一个函数,并且 promise1 状态是 fulfilled,那么 promise2 一定会接受到与 promse1 一样的值 value)

2.2.7.4:If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

(如果 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函数封装了多个内部状态(通过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进行了以下三点的改进:

  1. 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
  2. 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
  3. 更广的适用性。 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);
  });
}

Reference

es6.ruanyifeng.com/#docs/gener…