Promise是如何实现异步编程的?
Promise标准
不能免俗地贴个Promise标准链接Promises/A+。ES6的Promise有很多方法,包括Promise.all()/Promise.resolve()/Promise.reject()等,但其实这些都是Promises/A+规范之外的,Promises/A+规范只定义了一个Promise.then()方法,这是Promise的核心。
基本结构
new Promise((resolve, reject) => {
  let a = 0;
  if (a > 1) {
    resolve(a);
  } else {
    reject(a);
  }
}).then(res => {
  console.log(res);
}, err => {
  console.log(err);
})
Promise接收一个函数作为参数,我们称之为executor,该函数有两个参数resolve和reject,这两个参数也都是函数,并且,它们定义在Promise内部。
那么我们定义一个class并定义一个_isFunction方法,用来校验构造函数的参数必须是函数。再定义resolve和reject这两个方法。
class MyPromise{
  constructor(executor){
    if(!this._isFunction(executor)){
      throw new Error(`Promise resolver ${executor} is not a function`);
    }
  }
  _isFunction(val){
    return Object.prototype.toString.call(val) === '[object Function]';
  }
  _resolve(){
  }
  _reject(){
  }
}
Promise状态、resolve、reject
Promise有三种状态,分别是pending(等待中)、fulfilled(成功)、rejected(失败)。状态改变只能从pending => fulfilled,或者pending => rejected。
resolve的作用,就是将Promise的状态从pending改为fulfilled,它接收一个参数作为Promise执行成功的值,这个值会传给then的第一个回调函数。reject的作用是将Promise的状态从pending改为rejected,它也接收一个参数作为Promise执行失败的值,这个值会传给then的第二个回调函数。
那么我们定义好状态_status、_resolve、_reject,再定义两个数组_handleFulfilled、_handleRejected,分别存放then的成功和失败回调集合。当用户调用resolve或reject方法后,开始异步调用_handleFulfilled或_handleRejected数组中的回调。
class MyPromise {
  constructor(executor) {
    if (!this._isFunction(executor)) {
      throw new Error(`${executor} is not a function`);
    }
    this._status = "pending";
    this._value = undefined;
    this._handleFulfilled = [];
    this._handleRejected = [];
    // 很多文章在这里给executor加了try catch,实际上原生Promise的executor中的错误并没有捕获
    executor(this._resolve.bind(this), this._reject.bind(this));
  }
  _isFunction(val) {
    return Object.prototype.toString.call(val) === "[object Function]";
  }
  _resolve(value) {
    if(this._status === 'pending'){
      this._status = "fulfilled";
      this._value = value;
      let cb;
      // 异步按顺序调用并清空回调
      setTimeout(() => {
        while(cb = this._handleFulfilled.shift()){
          cb(value);
        }
      }, 0)
    }
  }
  _reject(value) {
    if(this._status === 'pending'){
      this._status = "rejected";
      this._value = value;
      let cb;
      // 异步按顺序调用并清空回调
      setTimeout(() => {
        while ((cb = this._handleRejected.shift())) {
          cb(value);
        }
      }, 0);
    }
  }
}
Promise.then
Promise.then定义了两个回调onFulfilled和onRejected
promise.then(onFulfilled, onRejected)
它们分别在Promise执行成功/失败时执行,它们都是可选的,Promises/A+规范规定,如果onFulfilled或onRejected不是函数,将被忽略,Promise会继续执行下一个then的回调。比如下面的例子会输出1,.then(2)则被忽略了。
new Promise((resolve, reject) => {
  resolve(1);
})
  .then(2)
  .then((res) => {
    console.log(res);
  });
then可以链式调用,是因为每个then都会返回一个新的Promise。then执行onFulfilled还是onRejected,取决于Promise的状态,如果Promise状态为pending,只会将onFulfilled和onRejected分别push到_handleFulfilled和_handleRejected数组;如果状态为fulfilled,会执行对应的onFulfilled;如果状态是rejected,执行对应的onRejected;
那么then方法的基本结构如下
then(onFulfilled, onRejected) {
    const self = this;
    const { _value, _status } = this;
  // 如果onFulfilled、onRejected不是函数,强制改为函数,并且该函数直接返回接收到的参数,传后面的then的回调函数
  onFulfilled = self._isFunction(onFulfilled) ? onFulfilled : (v) => v;
  onRejected = self._isFunction(onRejected) ? onRejected : (v) => v;
  return new MyPromise((resolve, reject) => {
    switch (_status) {
      case "pending":
        self._handleFulfilled.push(onFulfilled);
        self._handleRejected.push(onRejected);
        break;
      case "fulfilled":
        onFulfilled(_value);
        // todo
        break;
      case "rejected":
        onRejected(_value);
        // todo
        break;
      default:
        throw new Error('Promise resolver Unverified status');
        break;
    }
  });
}
在then链式调用的情况下,如果前一个then返回的是一个新Promise,后一个then的回调必须等这个新Promise的状态改变后才会执行。举例,下面的代码输出1之后,等待3秒才会输出2:
new Promise(resolve => {
  resolve()
}).then(() => {
  return new Promise(resolve => {
    console.log(1);
    setTimeout(() => {
      resolve()
    }, 3000)
  })
}).then(() => {
  console.log(2);
})
因此要对then的回调函数的返回值做个判断,如果返回值不是Promise,利用resolve直接返回这个值;如果返回值是Promise,就要等这个Promise状态变化之后再返回,而Promise状态变化之后一定会调用then的回调函数,利用这个特性,将resolve、reject作为then的回调函数即可。
then(onFulfilled, onRejected) {
    const self = this;
    const { _value, _status } = this;
    // 如果onFulfilled、onRejected不是函数,强制改为函数,并且该函数直接返回接收到的参数,传后面的then的回调函数
    onFulfilled = self._isFunction(onFulfilled) ? onFulfilled : (v) => v;
    onRejected = self._isFunction(onRejected) ? onRejected : (v) => v;
    return new MyPromise((resolve, reject) => {
      const fulfilled = (value) => {
        const res = onFulfilled(value);
        if (res instanceof MyPromise) {
          res.then(resolve, reject);
        } else {
          resolve(res);
        }
      };
      const rejected = (value) => {
        const res = onRejected(value);
        if (res instanceof MyPromise) {
          // 这里是重点
          res.then(resolve, reject);
        } else {
          reject(res);
        }
      };
      switch (_status) {
        case "pending":
          self._handleFulfilled.push(fulfilled);
          self._handleRejected.push(rejected);
          break;
        case "fulfilled":
          fulfilled(_value);
          break;
        case "rejected":
          rejected(_value);
          break;
        default:
          throw new Error('Promise resolver Unverified status');
          break;
      }
    });
  }
完整代码
class MyPromise {
  constructor(executor) {
    if (!this._isFunction(executor)) {
      throw new Error(`${executor} is not a function`);
    }
    this._status = "pending";
    this._value = undefined;
    this._handleFulfilled = [];
    this._handleRejected = [];
    // 很多文章在这里给executor加了try catch,实际上原生Promise的executor中的错误并没有捕获
    executor(this._resolve.bind(this), this._reject.bind(this));
  }
  _isFunction(val) {
    return Object.prototype.toString.call(val) === "[object Function]";
  }
  _resolve(value) {
    if (this._status === "pending") {
      this._status = "fulfilled";
      this._value = value;
      let cb;
      // 异步按顺序调用并清空回调
      setTimeout(() => {
        while ((cb = this._handleFulfilled.shift())) {
          cb(value);
        }
      }, 0);
    }
  }
  _reject(value) {
    if (this._status === "pending") {
      this._status = "rejected";
      this._value = value;
      let cb;
      // 异步按顺序调用并清空回调
      setTimeout(() => {
        while ((cb = this._handleRejected.shift())) {
          cb(value);
        }
      }, 0);
    }
  }
  then(onFulfilled, onRejected) {
    const self = this;
    const { _value, _status } = this;
    // 如果onFulfilled、onRejected不是函数,强制改为函数,并且该函数直接返回接收到的参数,传后面的then的回调函数
    onFulfilled = self._isFunction(onFulfilled) ? onFulfilled : (v) => v;
    onRejected = self._isFunction(onRejected) ? onRejected : (v) => v;
    return new MyPromise((resolve, reject) => {
      const fulfilled = (value) => {
        const res = onFulfilled(value);
        if (res instanceof MyPromise) {
          res.then(resolve, reject);
        } else {
          resolve(res);
        }
      };
      const rejected = (value) => {
        const res = onRejected(value);
        if (res instanceof MyPromise) {
          // 这里是重点
          res.then(resolve, reject);
        } else {
          reject(res);
        }
      };
      switch (_status) {
        case "pending":
          self._handleFulfilled.push(fulfilled);
          self._handleRejected.push(rejected);
          break;
        case "fulfilled":
          fulfilled(_value);
          break;
        case "rejected":
          rejected(_value);
          break;
        default:
          throw new Error('Promise resolver Unverified status');
          break;
      }
    });
  }
}
测试一下,先输出1,3秒后输出2,说明MyPromise的基本功能没问题了。
new MyPromise((resolve) => {
  console.log(1);
  setTimeout(() => {
    resolve(2);
  }, 3000)
}).then(res => {
  console.log(res);
})
最后,总结一下,Promise是如何实现异步编程的?
Promise接收一个函数为参数,传入了两个内部的方法resolve和reject,然后用then注册回调函数,手动调用resolve或reject就可以依次执行then的回调,并且给回调函数传值。如果then返回的也是Promise,同样的,手动调用resolve或reject后,才会继续往下执行。
其实本质上还是回调函数,只不过写法变了。
本文GitHub链接:Promise是如何实现异步编程的?
Promise是如何实现异步编程的?的更多相关文章
- ES6笔记(7)-- Promise异步编程
		
系列文章 -- ES6笔记系列 很久很久以前,在做Node.js聊天室,使用MongoDB数据服务的时候就遇到了多重回调嵌套导致代码混乱的问题. JS异步编程有利有弊,Promise的出现,改善了这一 ...
 - 我了解到的JavaScript异步编程
		
一. 一道面试题 前段时间面试,考察比较多的是js异步编程方面的相关知识点,如今,正好轮到自己分享技术,所以想把js异步编程学习下,做个总结. 下面这个demo 概括了大多数面试过程中遇到的问题: f ...
 - javascript异步编程方案汇总剖析
		
code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; backgr ...
 - Atitit.异步编程的发展历史 1.1. TAP & async/await
		
Atitit.异步编程的发展历史 1.1. TAP & async/await 1. 异步编程的发展历史1 1.1. Thread1 1.2. Task1 1.3. Async await2 ...
 - JavaScript异步编程解决方案探究
		
javascript的天生单线程特性,使得异步编程对它异常重要,早期的通常做法是用回调函数来解决.但是随着逻辑的复杂,和javascript在服务端的大显神通,使得我们很容易就陷入“回调陷井”的万丈深 ...
 - 简单实现异步编程promise模式
		
本篇文章主要介绍了异步编程promise模式的简单实现,并对每一步进行了分析,需要的朋友可以参考下 异步编程 javascript异步编程, web2.0时代比较热门的编程方式,我们平时码的时候也或多 ...
 - 你所必须掌握的三种异步编程方法callbacks,listeners,promise
		
目录: 前言 Callbacks Listeners Promise 前言 coder都知道,javascript语言运行环境是单线程的,这意味着任何两行代码都不能同时运行.多任务同时进行时,实质上形 ...
 - 延期(deferred)的承诺(promise) — jq异步编程浅析
		
引子 相信各位developers对js中的异步概念不会陌生,异步操作后的逻辑由回调函数来执行,回调函数(callback function)顾名思义就是“回头调用的函数”,函数体事先已定义好,在未来 ...
 - promise异步编程的原理
		
一.起源 JavaScript中的异步由来已久,不论是定时函数,事件处理函数还是ajax异步加载都是异步编程的一种形式,我们现在以nodejs中异步读取文件为例来编写一个传统意义的异步函数: var ...
 
随机推荐
- JDK 15已发布,你所要知道的都在这里!
			
JDK 15已经在2020年9月15日发布!详情见 JDK 15 官方计划.下面是对 JDK 15 所有新特性的详细解析! 官方计划 2019/12/12 Rampdown Phase One (fo ...
 - Kafka探究之路-命令小结
			
操作kafka之前,要先启动安装好的zk ,因为kafka的数据都保存在zk中,zk相当于是kafka的数据库吧. 安装的zk kafka 一定要按照书上,网上的教程,将相应的配置文件全部改成自己的, ...
 - 大数据-redis-redis启动出错
			
redis启动出错Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 解决方法(1) 首先如果你是从官方redis官 ...
 - moviepy音视频剪辑:使用fl_time进行时间特效处理报错ValueError: Attribute duration not set
			
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在使 ...
 - 第15.5节 PyQt的历史沿革介绍
			
当朋友向我推荐PyQt时,老猿才知道有这样一个在Python下的开源的图形界面开发工具,当准备学习PyQt安装时,发现要安装sip.PyQt.PyQt-tools,然后还要进行相关配置.老猿很好奇为什 ...
 - 【软件测试部署基础】maven的认识
			
最近部门分享测试环境部署相关内容,在同事的分享下,学到了很多新的知识点,也是我们在测试环境部署的时候非常重要的一些基本的知识点,当你系统的去了解了一下,你会发现后端在maven相关的点上有个清晰的了解 ...
 - Tensorflow学习笔记No.10
			
多输出模型 使用函数式API构建多输出模型完成多标签分类任务. 数据集下载链接:https://pan.baidu.com/s/1JtKt7KCR2lEqAirjIXzvgg 提取码:2kbc 1.读 ...
 - POWER BI 基于 ODBC 数据源的配置刷新-以Amazon Redshift为例
			
POWER BI 基于 ODBC 数据源的配置刷新-以Amazon Redshift为例 Powerbi 有多种数据源连接,可以使用它们连接到不同数据源. 如果在 Power BI Desktop 的 ...
 - Vue项目上线环境部署,项目优化策略,生成打包报告,及上线相关配置
			
Node.js简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境,用来方便快速地搭建易于扩展的网络应用.Node.js使用了一个事件驱动.非阻塞式I/O的模型,使其轻量 ...
 - Consul 多数据中心下的服务注册发现与配置共享
			
1. Consul简介 Consul是HashiCorp公司推出的开源软件,它提供了一套分布式高可用可横向扩展的解决方案,能为微服务提供服务治理.健康检查.配置共享等能力. Eurake2.x ...