前言

作为ES6处理异步操作的新规范,Promise一经出现就广受欢迎。面试中也是如此,当然此时对前端的要求就不仅仅局限会用这个阶段了。下面就一起看下Promise相关的内容。

Promise用法及实现

在开始之前,还是简单回顾下Promise是什么以及怎么用,直接上来谈实现有点空中花园的感觉。(下面示例参考自阮大佬es6 Promis,)

定义

Promise 是异步编程的一种解决方案,可以认为是一个对象,可以从中获取异步操作的信息。以替代传统的回调事件。

常见用法

Promise的创建

es6规范中,Promise是个构造函数,所以创建如下:

const promise = new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'resolve');
// 可以为同步,如下操作
return resolve('resolve')
})

注意resolve或者reject 一旦执行,后续的代码可以执行但就不会再更新状态(否则这状态回调就无法控制了)。

举个例子:

var a = new Promise((resolve,reject)=>{
resolve(1)
console.log('执行代码,改变状态')
throw new Error('ss')
})
a.then((res)=>{
console.log('resolved >>>',res)
},(err)=>{
console.log('rejected>>>',err)
}) // 输出
// 执行代码,改变状态
// resolved >>> 1

因此,状态更新函数之后的再次改变状态的操作都是无效的,例如异常之类的也不会被catch。

逻辑代码推荐在状态更新之前执行。

构造函数

构造函数接收一个函数,该函数会同步执行,即我们的逻辑处理函数,何时执行对应的回调,这部分逻辑还是要自己管理的。

至于如何执行回调,就和入参有关系了。

两个入参resolve和reject,分别更新不同状态,以触发对应处理函数。

触发操作由Promise内部实现,我们只关注触发时机即可

构造函数实现

那么要实现一个Promise,其构造函数应该是这么个样子:

// 三种状态
const STATUS = {
PENDING: 'pending',
RESOLVED:'resolved',
REJECTED:'rejected'
}
class Promise{
constructor(fn){
// 初始化状态
this.status = STATUS.PENDING
// resolve事件队列
this.resolves = []
// reject事件队列
this.rejects = []
// resolve和reject是内部提供的,用以改变状态。
const resovle = (val)=>{
// 显然这里应该是改变状态触发回调
this.triggerResolve(val)
}
const reject = (val)=>{
// 显然这里应该是改变状态触发回调
this.triggerReject(val)
}
// 执行fn
try{
fn(resolve,reject)
}catch(err){
// 运行异常要触发reject,就需要在这里catch了
this.triggerReject(err)
}
}
then(){
}
}

触发回调的triggerReject/triggerResolve 做的事情主要两个:

  1. 更新当前状态
  2. 执行回调队列中的事件
    // 触发 reject回调
triggerReject(val){
// 保存当前值,以供后面调用
this.value = val
// promise状态一经变化就不再更新,所以对于非pending状态,不再操作
if (this.status === STATUS.PENDING) {
// 更新状态
this.status = STATUS.REJECTED
// 循环执行回调队列中事件
this.rejects.forEach((it) => {
it(val)
})
}
}
// resolve 功能类似
// 触发 resolve回调
triggerResolve(val) {
this.value = val
if(this.status === STATUS.PENDING){
this.status = STATUS.RESOLVED
this.resolves.forEach((it,i)=>{
it(val)
})
}
}

此时执行的话还是不能达到目的的,因为this.resolves/ this.rejects的回调队列里面还是空呢。

下面就看如何会用then往回调队列中增加监听事件。

then用法

该方法为Promise实例上的方法,作用是为Promise实例增加状态改变时的回调函数。

接受两个参数,resolve和reject即我们所谓成功和失败回调,其中reject可选

then方法返回的是一个新的实例(也就是新建了一个Promise实例),可实现链式调用。

new Promise((resolve, reject) => {
return resolve(1)
}).then(function(res) {
// ...
}).then(function(res) {
// ...
});

前面的结果为后边then的参数,这样可以实现次序调用。

若前面返回一个promise,则后面的then会依旧遵循promise的状态变化机制进行调用。

then 实现

看起来也简单,then是往事件队列中push事件。那么很容易得出下面的代码:

// 两个入参函数
then(onResolved,onRejected){
const resolvehandle=(val)=>{
return onResolved(val)
},rejecthandle =(val)=>{
return onRejected(val)
}
// rejecthandle
this.resolves.push(resolvehandle)
this.rejects.push(rejecthandle)
}

此时执行示例代码,可以得到结果了。

new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'done');
}).then((res)=>{
console.log(res)
}) // done

不过这里太简陋了,而且then还有个特点是支持链式调用其实返回的也是promise 对象。

我们来改进一下。

then支持链式调用

 then(onResolved,onRejected){
// 返回promise 保证链式调用,注意这里每次then都新建了promise
return new Promise((resolve,reject)=>{
const resolvehandle = (val)=>{
// 对于值,回调方法存在就直接执行,否则不变传递下去。
let res = onResolved ? onResolved(val) : val
if(Promise.isPromise(res)){
// 如果onResolved 是promise,那么就增加then
return res.then((val)=>{
resolve(val)
})
}else {
// 更新状态,执行完了,后面的随便
return resolve(val)
}
},
rejecthandle = (val)=>{
var res = onRejected ? onRejected(val) : val;
if (Promise.isPromise(res)) {
res.then(function (val) {
reject(val);
})
} else {
reject(val);
}
}
// 正常加入队列
this.resolves.push(resolvehandle)
this.rejects.push(rejecthandle)
})
}

此时链式调用和promise 的回调也已经支持了,可以用如下代码测试。

new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'done');
}).then((res)=>{
return new Promise((resolve)=>{
console.log(res)
setTimeout(resolve, 200, 'done2');
})
}).then((res)=>{
console.log('second then>>', res)
})

同步resolve的实现

不过此时对于同步的执行,还是有些问题。

因为then中的实现,只是将回调事件假如回调队列。

对于同步的状态,then执行在构造函数之后,

此时事件队列为空,而状态已经为resolved,

所以这种状态下需要加个判断,如果非pending状态直接执行回调。

 then(onResolved,onRejected){
/**省略**/
// 刚执行then 状态就更新,那么直接执行回调
if(this.status === STATUS.RESOLVED){
return resolvehandle(this.value)
}
if (this.status === STATUS.REJECTED){
return rejecthandle(this.value)
}
})
}

这样就能解决同步执行的问题。

new Promise((resolve, reject) => {
resolve('done')
}).then((res)=>{
console.log(res)
})
// done

catch

catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

直接看例子比较简单:

getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});

此时catch是是getJSON和第一个then运行时的异常,如果只是在then中指定reject函数,那么then中执行的异常无法捕获。

因为then返回了一个新的promise,同级的reject回调,不会被触发。

举个例子:

var a = new Promise((resolve,reject)=>{
resolve(1)
})
a.then((res)=>{
console.log(res)
throw new Error('then')
},(err)=>{
console.log('catch err>>>',err) // 不能catch
})

该catch只能捕获构造函数中的异常,对于then中的error就不能捕获了。

var a = new Promise((resolve,reject)=>{
resolve(1)
})
a.then((res)=>{
console.log(res)
throw new Error('then')
}).catch((err)=>{
console.log('catch err>>>',err) // catch err>>> Error: then at <anonymous>:6:11
})

推荐每个then之后都跟catch来捕获所有异常。

catch 的实现

基于catch方法是.then(null, rejection)或.then(undefined, rejection)的别名这句话,其实实现就比较简单了。

其内部实现调用then就可以了。

catch(onRejected){
return this.then(null, onRejected)
}

Promise.resolve/Promise.reject

该方法为获取一个指定状态的Promise对象的快捷操作。

直接看例子比较清晰:

Promise.resolve(1);
// 等价于
new Promise((resolve) => resolve(1));
Promise.reject(1);
// 等价于
new Promise((resolve,reject) => reject(1));

既然是Promise的自身属性,那么可以用es6的static来实现:

Promise.reject与其类似,就不再实现了。

    // 转为promise resolve 状态
static resolve(obj){
if (Promise.isPromise(obj)) {
return obj;
}
// 非promise 转为promise
return new Promise(function (resolve, reject) {
resolve(obj);
})
}

结束语

参考文章

阮一峰es6入门

https://promisesaplus.com/

http://liubin.org/promises-book/#chapter1-what-is-promise

本想把常见的promise面试题一起加上的,后面就写成了promise的实现,手动Promise都可以实现的话,相关面试题应该问题不大。这里附一个JavaScript | Promises interiew 大家可以看看。完整代码请戳

Promise原理探究及实现的更多相关文章

  1. Promise 原理探究及其简单实现

    可移步 http://donglegend.com/2016/09/11/promise%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/ 观看 Promise是个什么玩意,大 ...

  2. Promise学习探究

    学习熟知吧,原理还是继续吧 例子1: var isGeted; function getRet(){ return new Promise(function(resolve, reject) { // ...

  3. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  4. [原] KVM 虚拟化原理探究 —— 目录

    KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...

  5. [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化

    KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...

  6. [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化

    KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...

  7. [原] KVM 虚拟化原理探究(4)— 内存虚拟化

    KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...

  8. [原] KVM 虚拟化原理探究(3)— CPU 虚拟化

    KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...

  9. [原] KVM 虚拟化原理探究(2)— QEMU启动过程

    KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...

随机推荐

  1. Win10《芒果TV》商店版跻身Windows商店《热门免费应用》前12强

    2017立春上班的第一天,让人惊喜的好日子,春节过后,Win10<芒果TV>商店版跻身Windows商店<热门免费应用>前12强,露出尖尖头,这个来自广大用户和合作伙伴们一直以 ...

  2. MASMPlus汇编之简单窗体

    .386 .model flat,stdcall option casemap:none ;include 定义 include   windows.inc include   gdi32.inc i ...

  3. shell转义符

    转义是一种引用单个字符的方法. 一个前面放上转义符 (\)的字符就是告诉shell这个字符按照字面的意思进行解释, 换句话说, 就是这个字符失去了它的特殊含义. 在某些特定的命令和工具中, 比如ech ...

  4. phpstudy+phpstorm+debug

    文:phpstudy+phpstorm+debug 一.配置前说明: 1.phpStudy集成了XDebug扩展,所以不用单独下载XDebug. 2.打开XDebug扩展:其它选项菜单 > PH ...

  5. DOTNET CORE DATETIME在LINUX与WINDOWS时间不一致

    .net core项目,部署到CentOS上的时候,发现DateTime.Now获取的时间与Windows不一致,主要是时区不一致. static void Main(string[] args) { ...

  6. ASP.NET MVC5快速入门--MyFirstWeb并发布到Windows Azure上

    博主刚刚学习ASP.NET MVC5,看着微软的文档一点点学,就把FirstWeb的建立展示一下下啦,本次建立一个带个人身份验证的例子(即有注册登录机制的动态网页),开始,啦啦啦~~ 新建一个项目,选 ...

  7. c# 查询本机可用的代理ip

    现在有很多网站都提供免费的代理ip,但是你会发现很多网站显示的可以用的 ,在自己电脑上是用不了,写个小代码提取出自己电脑上可以用的代理,没什么技术含量,只是为了记录一下 string strUrl = ...

  8. 为什么你有10年经验,但成不了专家?(重复性刻意训练+反馈修正,练习的精髓是要持续地做自己做不好的,太精彩了)真正的高手都有很强的自学能力,老师和教练的最重要作用是提供即时的反馈(莫非我从小到大学习不好的原因在这里?没有单独刻意训练?) good

    也许简单看书就是没有刻意训练.更没有反馈,所以没有效果 我倒是想起自己,研究VCL源码的时候,都是自己给自己提问,然后苦思冥想.自己解决问题,然后Windows编程水平果然上了一个台阶.对什么叫做“框 ...

  9. CrashRpt_v.1.4.2_vs2008_also_ok

    1.windows多线程程序release版崩溃记录工具,便于该如何查找错误. 2.此工具主要用来配置windbug工具,一种排查程序发布版本崩溃这种非常难处理的缺陷的方法,非常棒,amazing! ...

  10. 自动获取淘宝API数据访问的SessionKey

    最近在忙与淘宝做对接的工作,总体感觉淘宝的api文档做的还不错,不仅有沙箱测试环境,而且对于每一个api都可以通过api测试工具生成想要的代码,你完全可以先在测试工具中测试之后再进行代码的编写,这样就 ...