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是如何实现异步编程的?的更多相关文章

  1. ES6笔记(7)-- Promise异步编程

    系列文章 -- ES6笔记系列 很久很久以前,在做Node.js聊天室,使用MongoDB数据服务的时候就遇到了多重回调嵌套导致代码混乱的问题. JS异步编程有利有弊,Promise的出现,改善了这一 ...

  2. 我了解到的JavaScript异步编程

    一. 一道面试题 前段时间面试,考察比较多的是js异步编程方面的相关知识点,如今,正好轮到自己分享技术,所以想把js异步编程学习下,做个总结. 下面这个demo 概括了大多数面试过程中遇到的问题: f ...

  3. javascript异步编程方案汇总剖析

    code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; backgr ...

  4. Atitit.异步编程的发展历史 1.1. TAP & async/await

    Atitit.异步编程的发展历史 1.1. TAP & async/await 1. 异步编程的发展历史1 1.1. Thread1 1.2. Task1 1.3. Async await2 ...

  5. JavaScript异步编程解决方案探究

    javascript的天生单线程特性,使得异步编程对它异常重要,早期的通常做法是用回调函数来解决.但是随着逻辑的复杂,和javascript在服务端的大显神通,使得我们很容易就陷入“回调陷井”的万丈深 ...

  6. 简单实现异步编程promise模式

    本篇文章主要介绍了异步编程promise模式的简单实现,并对每一步进行了分析,需要的朋友可以参考下 异步编程 javascript异步编程, web2.0时代比较热门的编程方式,我们平时码的时候也或多 ...

  7. 你所必须掌握的三种异步编程方法callbacks,listeners,promise

    目录: 前言 Callbacks Listeners Promise 前言 coder都知道,javascript语言运行环境是单线程的,这意味着任何两行代码都不能同时运行.多任务同时进行时,实质上形 ...

  8. 延期(deferred)的承诺(promise) — jq异步编程浅析

    引子 相信各位developers对js中的异步概念不会陌生,异步操作后的逻辑由回调函数来执行,回调函数(callback function)顾名思义就是“回头调用的函数”,函数体事先已定义好,在未来 ...

  9. promise异步编程的原理

    一.起源 JavaScript中的异步由来已久,不论是定时函数,事件处理函数还是ajax异步加载都是异步编程的一种形式,我们现在以nodejs中异步读取文件为例来编写一个传统意义的异步函数: var ...

随机推荐

  1. LaTeX中的浮动体

    浮动体代码及注释: 显示效果:

  2. vs2019 Com组件初探-简单的COM编写以及实现跨语言调用

    前提条件 1.掌握C++基础语法 2.平台安装 vs2019 3.本地平台为 windows 10 1909 X64 4.了解vbs基础语法 本次目标 1.掌握Com组件的概念及原理 2.编写一个简单 ...

  3. Django 的缓存机制

    一 缓存介绍: 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会 ...

  4. java并发编程实战《六》等待-通知机制

    用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...

  5. 5.3 Spring5源码--Spring AOP使用接口方式实现

    Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解. 本文重点介绍Spring使用接口方式实现AOP. 使用接口方式实现AOP以了解为目的. 更好地理解动态 ...

  6. moviepy音视频剪辑:headblur的参数r_blur卷积核以及fx、fy、r_zone的功能作用及用途

    ☞ ░ 前往老猿Python博文目录 ░ 在moviepy1.03版本中,headblur的调用语法为:headblurbak(clip,fx,fy,r_zone,r_blur=None) 其中参数f ...

  7. Oracle函数使用1

    一.字符串处理函数 1.ascii(x):返回字符的ASCII. SQL语句:select ascii('a') from dual; dual:空表,每创建一个用户都会生成这样一个dual表,表中只 ...

  8. [BJDCTF2020]ZJCTF,不过如此 php伪协议, preg_replace() 函数/e模式

    转自https://www.cnblogs.com/gaonuoqi/p/12499623.html 题目给了源码 <?php error_reporting(0); $text = $_GET ...

  9. LeetCode初级算法之数组:1 两数之和

    两数之和 题目地址:https://leetcode-cn.com/problems/two-sum/ 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个整 ...

  10. C++异常之五 异常和继承

    异常和继承 异常也是类,我们可以创建自己的异常类,在异常中可以使用(虚函数,派生,引用传递和数据成员等), 下面用一个自制的数组容器Vector,在对Vector初始化时来对Vector的元素个数进行 ...