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. How Cocoa Beans Grow And Are Harvested Into Chocolate

    What is Cocoa Beans Do you like chocolate? Most people do. The smooth, brown candy is deliciously sw ...

  2. Django线上部署实战教程之Nginx+Gunicorn+Django篇

    #############################################   推荐 nginx supervisor gunicorn  配置简单,运维方便. Nginx (engi ...

  3. 用windows下的Anaconda搭建Django虚拟环境

    Django 是一个Python定制框架,可用于简便.快速的开发数据库驱动的web站点. 要使用Django,首先要建立一个虚拟工作环境. 那么,为什么要搭建虚拟环境呢?我们来看以下的开发场景: 假设 ...

  4. arduino basic issue

    1.    string char Str1[15]; char Str2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'}; char Str3[8] = {'a', ...

  5. html,将元素水平,垂直居中的四种方式

    将元素垂直,水平居中分两种情况:一个是元素尺寸固定,二是元素尺寸不固定 一.尺寸固定 方法1:定位 ,50%,margin负距 .box{ width: 400px; height: 300px; b ...

  6. Word Flow:创造吉尼斯世界纪录的触屏文本输入的全新体验——微软Windows Phone 8.1系统倾情巨献

    Flow:创造吉尼斯世界纪录的触屏文本输入的全新体验--微软Windows Phone 8.1系统倾情巨献" title="Word Flow:创造吉尼斯世界纪录的触屏文本输入的全 ...

  7. Python: Socket网络编程,多线程处理小Demo

    一个简单的例子,深入研究一下socket的多线程处理任务 Server端: #!/usr/bin/env python #encoding:utf8 # # 注意:定义encoding时必须在第二行 ...

  8. Java IO: System.in, System.out, System.err

    原文链接 作者: Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) System.in, System.out, System.err这3个流同样是常见的数据来 ...

  9. 链终止法|边合成边测序|Bowtie|TopHat|Cufflinks|RPKM|FASTX-Toolkit|fastaQC|基因芯片|桥式扩增|

    生物信息学 Sanger采用链终止法进行测序 带有荧光基团的ddXTP+其他四种普通的脱氧核苷酸放入同一个培养皿中,例如带有荧光基团的ddATP+普通的脱氧核苷酸A.T.C.G放入同一个培养皿,以此类 ...

  10. (一)mybatis简易搭建

    mybatis(基础及其搭建) 声明:该文章及该分类中的内容均基于正在开发的项目和一本参考书(深入浅出MyBatis技术原理与实战    by 杨开振) 一.mybatis核心组件(简要介绍) Sql ...