<!doctype html>promisehtml
简单来讲 就是为了解决callback hell
简单来看一个回调的例子vue
let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data)
})
})
})
因为括号过多过于复杂很难判断具体 某函数回调是哪个。在引入了promise
后 事情就变得简单了不少了。node
xxxxxxxxxx
let fs = require('fs')
function read(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(error,data){
error && reject(error)
resolve(data)
})
})
}
read('./a.txt')
.then(data=>{
return read(data)})
.then(data=>{
return read(data)})
.then(data=>{
console.log(data)
})
首先咱们要知道本身手写一个Promise
,应该怎么去写,谁来告诉咱们怎么写,须要遵循什么样的规则。固然这些你都不用担忧,其实业界都是经过一个规则指标来生产Promise
的。让咱们来看看是什么东西。Promise/A+web
Constructor表示构造函数,先来实现一个最简单的promise。分析一下,最简单的promise包含如下几种主要功能windows
1.执行函数
数组
2.reslove
[做为成功调用时候的回调函数]promise
3.reject
[做为失败调用时候的回调函数]app
那么简单来实现的话 应该是下面这种框架框架
xxxxxxxxxx
new promise =
function (执行函数 )
{
reslove ()
reject ()
}
Okk 那么如今按照ES6 类的方法来完善上面的伪代码:less
xxxxxxxxxx
class Promise{
constructor(executor){
//控制状态,使用了一次以后,接下来的都不被使用
this.status = 'pendding'
this.value = undefined
this.reason = undefined
//定义resolve函数
let resolve = (data)=>{
//这里pendding,主要是为了防止executor中调用了两次resovle或reject方法,而咱们只调用一次
if(this.status==='pendding'){
this.status = 'resolve'
this.value = data
}
}
//定义reject函数
let reject = (data)=>{
if(this.status==='pendding'){
this.status = 'reject'
this.reason = data
}
}
//executor方法可能会抛出异常,须要捕获
try{
//将resolve和reject函数给使用者
executor(resolve,reject)
}catch(e){
//若是在函数中抛出异常则将它注入reject中
reject(e)
}
}
}
若是要说promise
的话,大部分人的第一印象都是then
回调。可见then
方法对于promise
的重要性。
简单来讲,then
的功能就是,传入成功和失败两种状况下的回调函数,then方法出现,便于promise的更简洁使用,其基础功能代码也很简单:
xxxxxxxxxx
then(onFufilled,onRejected)
{
if(this.status === 'resolve'){
onFufilled(this.value)
}
if(this.status === 'reject'){
onRejected(this.reason)
}
}
onFulfilled 特性 若是 onFulfilled 是函数:
当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
在 promise 执行结束前其不可被调用
其调用次数不可超过一次
onRejected 特性 若是 onRejected 是函数:
当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
在 promise 被拒绝执行前其不可被调用
其调用次数不可超过一次
刚刚其实只能应对同步的状况,异步的时候就须要借助callback
来进行实现了,当reslove
reject
被执行后 去执行对应的callback
xxxxxxxxxx
//存放成功回调的函数
this.onResolvedCallbacks = []
//存放失败回调的函数
this.onRejectedCallbacks = []
let resolve = (data)=>{
if(this.status==='pendding'){
this.status = 'resolve'
this.value = data
//监听回调函数
this.onResolvedCallbacks.forEach(fn=>fn())
}
}
let reject = (data)=>{
if(this.status==='pendding'){
this.status = 'reject'
this.reason = data
this.onRejectedCallbacks.forEach(fn=>fn())
}
}
那么对于then
咱们也须要添加一些东西来进行适配,判断当Promise是一步操做时候,须要在咱们以前定义的回调函数组中添加一个回调函数
xxxxxxxxxx
if(this.status === 'pendding'){
this.onResolvedCallbacks.push(()=>{
// to do....
let x = onFufilled(this.value)
resolvePromise(promise2,x,resolve,reject)
})
this.onRejectedCallbacks.push(()=>{
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
})
}
这也是Promise
中的重头戏,咱们在用Promise的时候可能会发现,当then函数中return了一个值,咱们能够继续then下去,不过是什么值,都能在下一个then中获取,还有,当咱们不在then中放入参数,例:promise.then().then()
,那么其后面的then依旧能够获得以前then返回的值,来看看具体的实现吧
xxxxxxxxxx
then(onFufilled,onRejected){
//解决onFufilled,onRejected没有传值的问题
onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
//由于错误的值要让后面访问到,因此这里也要跑出个错误,否则会在以后then的resolve中捕获
onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
//声明一个promise对象
let promise2
if(this.status === 'resolve'){
//由于在.then以后又是一个promise对象,因此这里确定要返回一个promise对象
promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
//由于穿透值的缘故,在默认的跑出一个error后,不能再用下一个的reject来接受,只能经过try,catch
try{
//由于有的时候须要判断then中的方法是否返回一个promise对象,因此须要判断
//若是返回值为promise对象,则须要取出结果看成promise2的resolve结果
//若是不是,直接做为promise2的resolve结果
let x = onFufilled(this.value)
//抽离出一个公共方法来判断他们是否为promise对象
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
}
if(this.status === 'reject'){
promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try{
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
}
if(this.status === 'pendding'){
promise2 = new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{
// to do....
setTimeout(()=>{
try{
let x = onFufilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
try{
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
})
})
}
return promise2 //首先要确保then返回值为promise 才能够保证then的链式循环
}
1.返回Promise
?:首先咱们要注意的一点是,then有返回值,then了以后还能在then,那就说明以前的then返回的必然是个Promise
。
2.为何外面要包一层setTimeout
?:由于Promise自己是一个异步方法,属于微任务一列,必须得在执行栈执行完了在去取他的值,因此全部的返回值都得包一层异步setTimeout。
3.为何开头有两个判断?:这就是以前想要解决的若是then函数中的参数不是函数,那么咱们须要作处理。若是onFufilled不是函数,就须要自定义个函数用来返回以前resolve的值,若是onRejected不是函数,自定义个函数抛出异常。这里会有个小坑,若是这里不抛出异常,会在下一个then的onFufilled中拿到值。又由于这里抛出了异常因此全部的onFufilled或者onRejected都须要try/catch,这也是Promise/A+的规范。固然本人以为成功的回调不须要抛出异常也能够,你们能够仔细想一想。
4.resolvePromise
是什么?:这实际上是官方Promise/A+的需求。由于你的then能够返回任何职,固然包括Promise
对象,而若是是Promise
对象,咱们就须要将他拆解,直到它不是一个Promise
对象,取其中的值。
它的做用是用来将onFufilled的返回值进行判断取值处理,把最后得到的值放入最外面那层的
Promise
的resolve函数中。
xxxxxxxxxx
function resolvePromise(promise2,x,resolve,reject){
//判断x和promise2之间的关系
//由于promise2是上一个promise.then后的返回结果,因此若是相同,会致使下面的.then会是同一个promise2,一直都是,没有尽头
if(x === promise2){//至关于promise.then以后return了本身,由于then会等待return后的promise,致使本身等待本身,一直处于等待
return reject(new TypeError('循环引用'))
}
//若是x不是null,是对象或者方法
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
//为了判断resolve过的就不用再reject了,(好比有reject和resolve的时候)
let called
try{//防止then出现异常,Object.defineProperty
let then = x.then//取x的then方法可能会取到{then:{}},并无执行
if(typeof then === 'function'){
//咱们就认为他是promise,call他,由于then方法中的this来自本身的promise对象
then.call(x,y=>{//第一个参数是将x这个promise方法做为this指向,后两个参数分别为成功失败回调
if(called) return;
called = true
//由于可能promise中还有promise,因此须要递归
resolvePromise(promise2,y,resolve,reject)
},err=>{
if(called) return;
called = true
//一次错误就直接返回
reject(err)
})
}else{
//若是是个普通对象就直接返回resolve做为结果
resolve(x)
}
}catch(e){
if(called) return;
called = true
reject(e)
}
}else{
//这里返回的是非函数,非对象的值,就直接放在promise2的resolve中做为结果
resolve(x)
}
}
简单来讲,就是用来捕获Promise中rejecet的值,也就是至关于then方法中的OnRejected回调函数
xxxxxxxxxx
catch(onRejected){
return this.then(null,onRejected)
}
该方法是挂在Promise原型上的方法。当咱们调用catch传callback的时候,就至关因而调用了then方法。
对于Promsie.reslove
Promise.reject
来说,它本质是返回一个Promise对象。
xxxxxxxxxx
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
})
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
})
}
all方法能够说是Promise中很经常使用的方法了,它的做用就是将一个数组的Promise对象放在其中,当所有resolve的时候就会执行then方法,当有一个reject的时候就会执行catch,而且他们的结果也是按着数组中的顺序来排放的,那么咱们来实现一下。
xxxxxxxxxx
//all方法(获取全部的promise,都执行then,把结果放到数组,一块儿返回)
Promise.all = function(promises){
let arr = []
let i = 0
function processData(index,data){
arr[index] = data
i++
if(i == promises.length){
resolve(arr)
}
}
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data)
},reject)
}
})
}
其原理就是将参数中的数组取出遍历,每当执行成功都会执行
processData
方法,processData
方法就是用来记录每一个Promise的值和它对应的下标,当执行的次数等于数组长度时就会执行resolve,把arr的值给then。这里会有一个坑,若是你是经过arr数组的长度来判断他是否应该resolve的话就会出错,为何呢?由于js数组的特性,致使若是先出来的是1位置上的值进arr,那么0位置上也会多一个空的值,因此不合理
xxxxxxxxxx
Promise.all = function (p) {
let arr = []
let i = 0
function processDate(index, data) {
arr[index] = data
i++
if (i == p.length) {
resolve(arr)
}
}
return new Promise((resolve, reject) => {
for (let i = 0; i < p.length; i++) {
p(i).then (data => {
processDate(i,data)
},reject)
}
})
}
race方法虽然不经常使用,可是在Promise方法中也是一个能用得上的方法,它的做用是将一个Promise
数组放入race中,哪一个先执行完,race就直接执行完,并从then中取值。咱们来实现一下吧。
xxxxxxxxxx
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
}
})
}