Javascript语言的执行环境是"单线程"(single thread)。所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

异步模式"编程的4种方法:回调函数、事件监听、发布/订阅、Promises对象
还有generator、async/await.

本文尝试说一下对Promise的理解及如何实现。
1.Promise原理

promise对象有三种状态,pending、fulfilled和rejected。promise对象内部保存一个需要执行一段时间的异步操作,当异步操作执行结束后可以调用resolve或reject方法,来改变promise对象的状态,状态一旦改变就不能再变。new一个promise后可以通过then方法,指定resolved和rejected时的回调函数。下面是我们日常使用Promise的代码逻辑。
let p = new Promise((resolve,reject)=>{

    $.ajax({
url: "../www/data.txt",
dataType: "json",
success(data){
resolve(data);
},
error(err){
reject(err);
}
});
});
p.then(function(data){
alert("success"+data);
},function(err){
alert("failed");
})

结合Promise A+规范,我们就可以分析一下我们要实现一个什么东西:

实现一个状态机,有三个状态,pending、fulfilled、rejected,状态之间的转化只能是pending->fulfilled、pending->rejected,状态变化不可逆。
实现一个then方法,可以用来设置成功和失败的回调
then方法要能被调用多次,所以then方法需要每次返回一个新的promise对象,这样才能支持链式调用。
构造函数内部要有一个value值,用来保存上次执行的结果值,如果报错,则保存的是异常信息。
2.实现原理

2.1实现状态机

那我们现在就按照上面提到的原理和规范来实现这个Promise构造函数。
class myPromise {

    constructor(executor) {
this.status = PENDING;
this.value = '';
this.Resolve = this.resolve.bind(this);
this.Reject = this.reject.bind(this);
this.then= this.then.bind(this);
executor(this.Resolve, this.Reject);
} resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
} reject(value) {
if (this.status === PENDING) {
this.value = value;
this.status = REJECTED;
}
} then(onfulfilled, onrejected) {
if (this.status === FULFILLED) {
onfulfilled(this.value);
}
if (this.status === REJECTED) {
onrejected(this.value);
}
}
} const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected'; const test = new myPromise((resolve, reject) => {
resolve(100);
});
test.then((data) => {
console.log(data);
}, (data) => {
});

因为Promise是一个构造函数,使用ES6的写法,首先想到的就是有显式constructor声明的class。上面就是我们用class的实现,可以看到这样我们就实现了这个状态机,有status, value两个属性和resolve, reject, then三个函数;同时它有pending, fulfilled和rejected三个状态,其中pending就可以切换为fulfilled或者rejected两种。

运行一下,输出了100,但是现在其实不是一个异步处理方案,代码先运行了resolve(100)然后又运行了then函数,其实对于异步的情况没有处理,不信的话就给resolve加一个setTimeout,好了,代码又没有输出了。

2.2 实现异步设置状态

作为一个异步处理的函数,在使用的时候,我们肯定是会先设置好不同异步返回后的处理逻辑(即then的成功、失败调用函数),然后安心等待异步执行,最后再异步结束后,系统会自动根据我们的逻辑选择调用不同回调函数。换句话说,then函数要对status为pending的状态进行处理。处理的原理是设置两个数组,在pending状态下分别保存成功和失败回调函数,当状态改变后,再根据状态去调用数组中保存的回调函数。

class myPromise {
constructor (executor) {

this.status = PENDING;
this.value = '';
this.onfulfilledArr = [];
this.onrejectedArr = [];
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
this.then = this.then.bind(this);
executor(this.resolve, this.reject);

}

resolve (value) {

if (this.status === PENDING) {
this.value = value;
this.onfulfilledArr.forEach(item => {
item(this.value);
})
this.status = FULFILLED;
}

}

reject (value) {

if (this.status === PENDING) {
this.value = value;
this.onrejectedArr.forEach(item => {
item(this.value);
})
this.status = REJECTED;
}

}

then (onfulfilled, onrejected) {

if (this.status === FULFILLED) {
onfulfilled(this.value);
}
if (this.status === REJECTED) {
onrejected(this.value);
}
if (this.status === PENDING) {
this.onfulfilledArr.push(onfulfilled);
this.onrejectedArr.push(onrejected);
}

}
}

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const test = new myPromise((resolve, reject) => {
setTimeout(() => {

resolve(100);

}, 2000)
});
test.then((data) => {
console.log(data);
},(data) => {});
可以这样理解

new myPromise 有异步代码

setTimeout(() => {
    resolve(100);
}, 2000)

js是单线程的,这个时候会先把这个任务添加到定时触发器线程中去(计时完毕后添加到事件队列中,等待js引擎空闲后执行),先去执行下面的同步代码
test.then((data) => {

    console.log(data);

},(data) => {});
完成输出及状态改变。

但是Promise的一大特点就是可以链式调用,即test.then(success, fail).then(success, fail)...这就需要then返回一个新的Promise对象,而我们的程序现在明显的是不支持的。那么继续改一下。

2.3 实现链式调用

再观察一下链式调用,如果成功和失败的函数中有返回值,这个值要作为参数传给下个then函数的成功或失败回调。所以我们要在返回的new Promise中调用相应的函数。

class myPromise {
constructor (executor) {

this.status = PENDING;
this.value = '';
this.onfulfilledArr = [];
this.onrejectedArr = [];
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
this.then = this.then.bind(this);
executor(this.resolve, this.reject);

}

resolve (value) {

if (this.status === PENDING) {
this.value = value;
this.onfulfilledArr.forEach(item => {
item(this.value);
})
this.status = FULFILLED;
}

}

reject (value) {

if (this.status === PENDING) {
this.value = value;
this.onrejectedArr.forEach(item => {
item(this.value);
})
this.status = REJECTED;
}

}

then (onfulfilled, onrejected) {

if (this.status === FULFILLED) {
const res = onfulfilled(this.value);
return new Promise(function(resolve, reject) {
resolve(res);
})
}
if (this.status === REJECTED) {
const res = onrejected(this.value);
return new Promise(function(resolve, reject) {
reject(res);
})
}
if (this.status === PENDING) {
const self = this;
return new Promise(function(resolve, reject) {
self.onfulfilledArr.push(() => {
const res = onfulfilled(self.value)
resolve(res);
});
self.onrejectedArr.push(() => {
const res = onrejected(self.value)
reject(res);
});
})
}

}
}

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const test = new myPromise((resolve, reject) => {
setTimeout(() => {

resolve(100);

}, 2000)
});
test.then((data) => {
console.log(data);
return data + 5;
},(data) => {})
.then((data) => {
console.log(data)
},(data) => {});

再运行一下,输出100,105。好了,一个简单的Promise就实现好了。

动手搞一个Promise的更多相关文章

  1. 死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  2. 掘金转载-手写一个Promise

    目录 一 什么是Promise ? 二 Promises/A+ 规范 2.1 术语 2.2 基本要求 2.2.1. Promise的状态 2.2.2. Then 方法 2.3 简易版实践 2.4 进一 ...

  3. Spring Security(五) —— 动手实现一个 IP_Login

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-5/ 「老徐」欢迎转载,保留摘要,谢谢! 5 动手实现一个IP_Login 在开始这篇文章之前,我们 ...

  4. 从如何使用到如何实现一个Promise

    前言 这篇文章我们一起来学习如何使用Promise,以及如何实现一个自己的Promise,讲解非常清楚,全程一步一步往后实现,附带详细注释与原理讲解. 如果你觉的这篇文章有帮助到你,️关注+点赞️鼓励 ...

  5. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

  6. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  7. 教你一步一步实现一个Promise

    Promise我想现在大家都非常熟悉了,主要作用就是解决异步回调问题,这里简单介绍下. Promise规范是CommonJS规范之一,而Promise规范又分了好多种,比如 Promises/A.Pr ...

  8. 操刀 requirejs,自己动手写一个

    前沿 写在文章的最前面 这篇文章讲的是,我怎么去写一个 requirejs . 去 github 上fork一下,顺便star~ requirejs,众所周知,是一个非常出名的js模块化工具,可以让你 ...

  9. 【原】手写一个promise

    上一篇文章中,我们介绍了Promise的基本使用,在这篇文章中,我们试着自己来写一个Promise,主要是学习Promise的内部机制,学习它的编程思想. !!!备注:本文写的不好,仅供自己学习之用, ...

随机推荐

  1. 2019-2020-1 20199324《Linux内核原理与分析》第六周作业

    第五章 系统调用的三层机制(下) 1.给MenuOS增加命令 进入Linuxkernel目录下,强制删除当前menu目录,再重新克隆一个新版本的menu 进入menu,运行make roofts脚本就 ...

  2. 数学中的距离distance(未完成)

    manhattan distance(曼哈顿距离) euclidean distance(欧几里得距离) cosine distance(cosine距离) 闵式距离 切比雪夫距离

  3. CentOS7部署yum环境及虚拟机快照克隆

    CentOS部署IP地址 第一种:nmtui        方向键.tab.空格.回车第二种:修改网卡配置文件         /etc/sysconfig/network-sripts/ifcfg- ...

  4. Spring Boot: Jdbc javax.net.ssl.SSLException: closing inbound before receiving peer's close_notify

    jdbc:mysql://127.0.0.1:3306/xxx?useSSL=false 在后面添加?useSSL=false即可 参考网站

  5. chkconfig原理

    ll  /etc/rc.d    里面有运行级别对应的脚本 chkconfig --list  sshd ll /etc/rc.d/rc3.d/   | grep sshd     (查看3启动 里面 ...

  6. 吴裕雄--天生自然python学习笔记:python用 Selenium 组件实现浏览器操作自动化

    一般情况下,我们都是用手工操作的方式来对浏览器进行各种操作 . 实际上, 只要我们安装一个自动化操作组件, Python 就可以让我们的很多操作实现自动化 . Selenium 组件 在开发网页时,用 ...

  7. ccpc20190823

    04 http://acm.hdu.edu.cn/showproblem.php?pid=6705 分析:先把每条边以 形式放进堆,堆按路径权值从小到大排序,然后每次取出堆顶,用v的出边扩展 新的路径 ...

  8. Qt 项目中main主函数及其作用

    main.cpp 是实现 main() 函数的文件,下面是 main.cpp 文件的内容. #include "widget.h" #include <QApplicatio ...

  9. jQuery插件开发小结

    jQuery插件开发规范 1. 使用闭包 (function($) { // Code goes here })(jQuery); 这是来自jQuery官方的插件开发规范要求,使用这种编写方式有什么好 ...

  10. MOOC(8)- 在excel中定义用例是否运行

    除了在配置文件中定义运行哪几条用例,还可以直接在excel中定义好是否运行用例,这样比起配置文件更加直观 在运行用例的时候判断一下是否运行这个字段即可