Promise学习

Promise学习

学习源于:

尚硅谷Web前端Promise教程从入门到精通

【全网首发:已完结】你不知道的『Promise』【基础+应用+源码】

你不知道的javascript【中卷】

Promise解决异步流程化的一种手段。

promise的参数是excutor执行器,这个执行器里包含两个函数,一个resolve,一个reject。且这个excutor执行器同步执行的,then方法是异步调用

解决回调地狱

来源:Promise 是怎么解决回调地狱问题的?

回调地狱有两个主要的问题:

  1. 多层嵌套的问题;
  2. 每种任务的处理结果存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性。

这两种问题在“回调函数时代”尤为突出,Promise 的诞生就是为了解决这两个问题。Promise 利用了三大技术手段来解决回调地狱:

  • 回调函数延迟绑定(回调函数不是直接声明的,而是通过后面的 then 方法传入的,即延迟传入)
  • 返回值穿透(根据 then 中回调函数的传入值创建不同类型的 Promise,然后把返回的 Promise 穿透到外层,以供后续的调用)
  • 错误冒泡(这样前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了)

new Promise

new Promise(function(…){…})模式通常称为revealing constructor。传入的函数会立即执行(不会像then…中的回调一样异步延迟),它有两个参数,分别称为resolve和reject。这些是promise的决议函数。resolve(…)通常标识完成,而reject(…)则标识拒绝。

Promise一旦决议则一直保持其决议结果(完成或拒绝)不变。

类型检验

既然Promise是new Promise创建的,你可能会认为可以通过instanceof Promise来检查,但遗憾的是,这并不足以作为检查方法。原因有许多:其中最重要的是Promise值可能是从其他浏览器窗口(iframe等)接收到的。这个浏览器窗口自己的Promise可能和当前窗口/frame的不同,因此这样的检查无法识别Promise实例。还有,库和框架可能会选择实现自己的Promise,而不是使用原生ES6的Promise实现。

因此识别Promise就是定义某种称为thenable的东西,将其定义为任何具有then方法的对象和函数。我们认为这样的值就是Promise一致的thenable。

于是,对thenable值得鸭子类型得检测就大致类似于:

if ( 
    p !== null && 
    ( 
        typeof p === "object" || 
        typeof p === "function" 
    ) && 
    typeof p.then === "function" 
) { 
    // 假定这是一个thenable! 
} 
else { 
    // 不是thenable 
}

如果你试图使用恰好有 then(…) 函数的一个对象或函数值完成一个 Promise,但并不希望它被当作 Promise 或 thenable,那就有点麻烦了,因为它会自动被识别为thenable,并被按照特定的规则处理。而且,如果有任何其他代码无意或恶意地给 Object.prototype、 Array.prototype 或任何其他原生原型添加 then(…),你无法控制无法预测。并且如果指定的是不调用其参数作为回调的函数,那么如果有 Promise 决议到这样的值,就会永远挂住!

在 ES6 之前,社区已经有一些著名的非 Promise 库恰好有名为 then(…) 的方法。这些库中有一部分选择了重命名自己的方法以避免冲突。而其他的那 些库只是因为无法通过改变摆脱这种冲突,就很不幸地被降级进入了“与基于 Promise 的 编码不兼容”的状态。标准决定劫持之前未保留的——听起来是完全通用的——属性名 then。这意味着所有值 (或其委托),不管是过去的、现存的还是未来的,都不能拥有 then(…) 函数,不管是有意的还是无意的;否则这个值在 Promise 系统中就会被误认为是一个 thenable,这可能会导致非常难以追踪的 bug。

Promise 信任问题

回调模式出现信任问题得原因在于回调函数得执行权在异步任务中,如果所有异步任务都能保证不出错就不会有信任问题。然而现实并非如此,而promise充当第三方协调机制来管理异步任务。如果promise机制中,任何得一步的协调对象不是标准的promise,也就会出现信任问题。所以promise每一步都返回标准的promise,这就保证了promise完全可信任。

调用过早

这个问题主要就是担心代码是否会引入类似 Zalgo 这样的副作用。在这类问题中,一个任务有时同步完成,有时异步完成,这可能会导致竞态条件。 根据定义,Promise 就不必担心这种问题,因为即使是立即完成的 Promise(类似于 new Promise(function(resolve){ resolve(42); }))也无法被同步观察到。

调用过晚

和前面一点类似,Promise 创建对象调用 resolve(…) 或 reject(…) 时,这个 Promise 的 then(…) 注册的观察回调就会被自动调度。可以确信,这些被调度的回调在下一个异步事件点上一定会被触发。但是,还有很重要的一点需要指出,有很多调度的细微差别。在这种情况下,两个独立 Promise 上链接的回调的相对顺序无法可靠预测。要避免这样的细微区别带来的噩梦,你永远都不应该依赖于不同 Promise 间回调的顺序和 调度。实际上,好的编码实践方案根本不会让多个回调的顺序有丝毫影响,可能的话就要避免。

不被调用

首先,没有任何东西(甚至 JavaScript 错误)能阻止 Promise 向你通知它的决议(如果它 决议了的话)。如果你对一个 Promise 注册了一个完成回调和一个拒绝回调,那么 Promise 在决议时总是会调用其中的一个。

当然,如果你的回调函数本身包含 JavaScript 错误,那可能就会看不到你期望的结果,但实际上回调还是被调用了。但是,如果 Promise 本身永远不被决议呢?即使这样,Promise 也提供了解决方案,其使用了一种称为竞态的高级抽象机制:

// 用于超时一个Promise的工具
function timeoutPromise(delay) { 
    return new Promise( function(resolve,reject){ 
        setTimeout( function(){ 
            reject( "Timeout!" ); 
        }, delay ); 
    } ); 
} 
// 设置foo()超时
Promise.race( [ 
    foo(), // 试着开始foo() 
    timeoutPromise( 3000 ) // 给它3秒钟
] ) 
    .then( 
    function(){ 
        // foo(..)及时完成!
    },
    function(err){ 
        // 或者foo()被拒绝,或者只是没能按时完成
        // 查看err来了解是哪种情况
    } 
); 

我们可以保证一个 foo() 有一个输出信号,防止其永久挂住程序。

调用次数过少或过多

Promise 的定义方式使得它只能被决议一次。如果出于某种 原因,Promise 创建代码试图调用 resolve(…) 或 reject(…) 多次,或者试图两者都调用, 那么这个 Promise 将只会接受第一次决议,并默默地忽略任何后续调用。

当然,如果你把同一个回调注册了不止一次(比如 p.then(f); p.then(f);),那它被调用的次数就会和注册次数相同。

未能传递所需的环境和参数

如果你没有用任何值显式决议,那么这个值就是 undefined。

如果使用多个参数调用 resovle(…) 或者 reject(…),第一个参数之 后的所有参数都会被默默忽略。如果要传递多个值,你就必须要把它们封装在单个值中传递,比如通过一个数组或对象。

吞掉可能出现的错误和异常

如果在 Promise 的创建过程中或在查看其决议 结果过程中的任何时间点上出现了一个 JavaScript 异常错误,比如一个 TypeError 或 ReferenceError,那这个异常就会被捕捉,并且会使这个 Promise 被拒绝。这是一个重要的细节,因为其有效解决了另外一个潜在的 Zalgo 风险,即出错可能会引起同步响应(回调模式),而不出错则会是异步的。Promise 甚至把 JavaScript 异常也变成了异步行为,进而极大降低了竞态条件出现的可能。

但是,如果 Promise 完成后在查看结果时(then(…) 注册的回调中)出现了 JavaScript 异 常错误会看起来像是 foo.bar() 产生的异常真的被吞掉了。别担心,实际上并不是这样。 但是这里有一个深藏的问题,就是我们没有侦听到它。为什么它不是简单地调用我们定义的错误处理函数呢?表面上的逻辑应该是这样啊。如果 这样的话就违背了 Promise 的一条基本原则,即 Promise 一旦决议就不可再变。除了违背原则之外,这样的行为也会造成严重的损害。因为假如这个 promise p 有多个 then(…) 注册的回调的话,有些回调会被调用,而有些则不会,情况会非常不透明,难以解释。

promise可信任吗

你肯定已经注意到 Promise 并没有完全摆脱回调。它们只是改变了传递回调的位置。由把回调函数交给异步处理变为由异步处理返回处理结果,交给我们后续做处理。

关于 Promise 的很重要但是常常被忽略的一个细节是,Promise 对这个问题已经有一个解决方案。包含在原生 ES6 Promise 实现中的解决方案就是 Promise.resolve(…)。

  • 如果向 Promise.resolve(…) 传递一个非 Promise、非 thenable 的立即值,就会得到一个用这个值填充的 promise。
  • 如果向 Promise.resolve(…) 传递一个真正的 Promise,就只会返回同一个promise
  • 如果向 Promise.resolve(…) 传递了一个非 Promise 的 thenable 值,前者就会试图展开这个值,而且展开过程会持续到提取出一个具体的非类 Promise 的最终值

Promise.resolve(…) 可以接受任何 thenable,将其解封为它的非 thenable 值。从 Promise. resolve(…) 得到的是一个真正的 Promise,是一个可以信任的值。如果你传入的已经是真正的 Promise,那么你得到的就是它本身,所以通过 Promise.resolve(…) 过滤来获得可信任性完全没有坏处。

Promise.resolve(…) 会将传入的真正 Promise 直接返回,对传入的 thenable 则会展开。如果这个 thenable 展开得到一个拒绝状态,那么从 Promise. resolve(…) 返回的 Promise 实际上就是这同一个拒绝状态。 所以对这个 API 方法来说,Promise.resolve(…) 是一个精确的好名字,因为它实际上的结果可能是完成或拒绝。

对于用 Promise.resolve(…) 为所有函数的返回值(不管是不是 thenable) 都封装一层。另一个好处是,这样做很容易把函数调用规范为定义良好的异步任务

Promise.resolve 的出现主要是解决识别 promise 的问题,使用该方法来进行格式化,返回的必定是真正的 promise,解决了因为对象或者函数中刚好有 then 方法造成的冲突和误使用,解决因为不是真正 promise 造成的程序被一直挂起并无法决议的问题,而且 Promise.resolve 处理真正 promise 是没有额外开销的,我们使用的时候没必要考虑性能问题。

链式流

我们可以把多个 Promise 连接到一起以表示一系列异步步骤。

这种方式可以实现的关键在于以下两个 Promise 固有行为特性:

  • 每次你对 Promise 调用 then(…),它都会创建并返回一个新的 Promise,我们可以将其 链接起来;
  • 不管从 then(…) 调用的完成回调(第一个参数)返回的值是什么,它都会被自动设置 为被链接 Promise(第一点中的)的完成

不管我们想要多少个异步步骤,每一步都能够根据需要等待下一步。一步步传递的值是可选的。如果不显式返回一个值,就会隐式返回 undefined,并且这些 promise 仍然会以同样的方式链接在一起。这样,每个 Promise 的决 议就成了继续下一个步骤的信号。

开发者常会遇到这样的情况:他们想要通过本身并不支持 Promise 的工具 (就像这里的 ajax(…),它接收的是一个回调)实现支持 Promise 的异步流 程控制。虽然原生 ES6 Promise 机制并不会自动为我们提供这个模式,但所 有实际的 Promise 库都会提供。通常它们把这个过程称为“提升”“promise 化”或者其他类似的名称。

如果你调用 promise 的 then(…),并且只传入一个完成处理函数,一个默认拒绝处理函数就会顶替上来。

  • 默认拒绝处理函数只是把错误重新抛出。从本质上说,这使得错误可以继续沿着 Promise 链传播下去,直到遇到显式定义的拒绝处理函数。
  • 默认的完成处理函数只是把接收到的任何传入值传递给下一个步骤 (Promise)而已

错误处理

对多数开发者来说,错误处理最自然的形式就是同步的 try…catch 结构。遗憾的是,它只能是同步的,无法用于异步代码模式。

为了避免丢失被忽略和抛弃的 Promise 错误,一些开发者表示,Promise 链的一个最佳实践就是最后总以一个 catch(…) 结束。如果通过无效的方式使用 Promise API,并且出现一个错误阻碍了正常的 Promise 构造,那么结果会得到一个立即抛出的异常,而不是一个被拒绝的Promise。

Promise 的错误处理易于出错,这非常容易造成错误被吞掉。因为异常可能出现在fulfii回调中,而没有在then返回的promise处理异常,导致异常被吞噬。在fullfill回调出现错误会抛给下一个then去解决。

有些 Promise 库增加了一些方法,用于注册一个类似于“全局未处理拒绝”处理函数的东 西,这样就不会抛出全局错误,而是调用这个函数。但它们辨识未捕获错误的方法是定义 一个某个时长的定时器,比如 3 秒钟,在拒绝的时刻启动。如果 Promise 被拒绝,而在定时器触发之前都没有错误处理函数被注册,那它就会假定你不会注册处理函数,进而就是未被捕获错误。

在实际使用中,对很多库来说,这种方法运行良好,因为通常多数使用模式在 Promise 拒 绝和检查拒绝结果之间不会有很长的延迟。但是这种模式可能会有些麻烦,因为 3 秒这个 时间太随意了(即使是经验值),也因为确实有一些情况下会需要 Promise 在一段不确定的 时间内保持其拒绝状态。而且你绝对不希望因为这些误报(还没被处理的未捕获错误)而 调用未捕获错误处理函数。

更常见的一种看法是:Promsie 应该添加一个 done(…) 函数,从本质上标识 Promsie 链的 结束。done(…) 不会创建和返回 Promise,所以传递给 done(…) 的回调显然不会报告一个 并不存在的链接 Promise 的问题。 那么会发生什么呢?它的处理方式类似于你可能对未捕获错误通常期望的处理方式: done(…) 拒绝处理函数内部的任何异常都会被作为一个全局未处理错误抛出(基本上是在开发者终端上)。

var p = Promise.resolve( 42 ); 
p.then( 
 function fulfilled(msg){ 
 // 数字没有string函数,所以会抛出错误
 console.log( msg.toLowerCase() ); 
 } 
) 
.done( null, handleErrors ); 
// 如果handleErrors(..)引发了自身的异常,会被全局抛出到这里

浏览器有一个特有的功能是我们的代码所没有的:它们可以跟踪并了解所有对象被丢弃以 及被垃圾回收的时机。所以,浏览器可以追踪 Promise 对象。如果在它被垃圾回收的时候 其中有拒绝,浏览器就能够确保这是一个真正的未捕获错误,进而可以确定应该将其报告 到开发者终端。

自定义promise

主体

function Promise(executor){
  //初始化
  this.PromiseState = 'padding';
  this.PromiseResult = null;
  //callbacks保存多个.then调用中的onResolved和onRejected
  this.callbacks = [];
  var self = this;

  //resolve
  function resolve(data){
    //只能调一次
    if(self.PromiseState !== 'padding') return;
    //注意this作用域
    self.PromiseState = 'fulfilled';
    self.PromiseResult = data;
    //异步callback回调执行
    self.callbacks.forEach(item =>{
      item.onResolved(data);
    })
  }

  //reject
  function reject(data){
    //只能调一次
    if(self.PromiseState !== 'padding') return;
    //注意this作用域
    self.PromiseState = 'rejected';
    self.PromiseResult = data;
    //异步callback回调执行
    self.callbacks.forEach(item => {
      item.onRejected(data);
    })
  }

  //解决throw
  try{
    executor(resolve,reject);
  }catch(e){
    reject(e);
  }

}


Promise.prototype.then  = function(onResolved,onRejected){
  const self = this;
  //调用回调函数,then函数返回一个promise对象
  return new Promise((resolve,reject)=>{
    //封装一个callback,参数type为onResolved或onRejected
    function callback(type){
      try{
        //获取这个回调函数的返回值
        let result = type(self.PromiseResult)
        //如果返回对象是promise
        if(result instanceof Promise){
          //那么将返回对象的结果赋值给res
          result.then(v=>{
            resolve(v);
          },r=>{
            reject(r);
          })
        }else{
          //如果返回对象不是promise直接将返回值赋给res【promise对象】的PromiseResult
          resolve(result);
        }        
      }catch(e){
        reject(e);
      }
    }
    //如果此时对象的PromiseState为fulfilled
    if(this.PromiseState=='fulfilled'){
       //then方法回调异步执行
      setTimeout(()=>{
        callback(onResolved);
      },0);
    }
    //如果此时对象的PromiseState为rejected
    if(this.PromiseState=='rejected'){
      //then方法回调异步执行
      setTimeout(()=>{
        callback(onRejected);
      },0);
    }
    //如果此时对象的PromiseState为padding
    //说明此时可能遇上了异步操作
    if(this.PromiseState=='padding'){
      //保存回调函数
      this.callbacks.push({
        onRejected:function(){
          callback(onRejected);
        },
        onResolved:function(){
          callback(onResolved);
        },
        })
    }
    }
  );
}


let p = new Promise((resolve,reject)=>{
    setTimeout(() => {
        // resolve('ok');
        reject('error');
    }, 1000);
})
console.log(p);
const res = p.then(value=>{
    console.log('value: '+value);
    return new Promise((resolve,reject)=>{
        // resolve('ok');
        reject('error');
    })
},reason=>{
    console.log('reason: '+reason);
    return new Promise((resolve,reject)=>{
        // resolve('ok');
        reject('error');
    })
})

console.log(res);

catch

Promise.prototype.catch = function(onRejected){
  return this.then(undefined,onRejected);
}

let p = new Promise((resolve,reject)=>{
    setTimeout(() => {
    	// resolve('ok');
    	reject('error');
    	}, 1000);
    })
    let res = p.catch(reason => {
    console.warn(reason);
})
console.log(res);

异常穿透

//判断回调函数的参数
//Promise.prototype.then最初判断
if(typeof onRejected !== 'function'){
    onRejected = reason=>{
        // console.log(reason);
        throw reason;
    }
    }
    if(typeof onResolved !=='function'){
        onResolved = value=>{
        // console.log(value);
        return value;
    };
}

let p = new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve('ok');
        // reject('ok');
    }, 1000);
})
p.then().then(value=>{
	console.log(222);
}).then(value=>{
	console.log(333);
}).catch(reason=>{
	console.warn(reason);
})

resolve

//Promise.resolve 函数对象,非实例对象
Promise.resolve = function(value){
  //返回promise对象
  return new Promise((resolve,reject)=>{
    if(value instanceof Promise){
      value.then(v=>{
        resolve(v);
      },r=>{
        reject(r);
      })
    }else{
      resolve(value);
    }
  })
}

let p = Promise.resolve('ok');
let p2 = Promise.resolve(new Promise((resolve,reject)=>{
    // resolve('success');
    reject('erro')
}))
let p3 = Promise.resolve(Promise.resolve('oh yeah'));
console.log(p);
console.log(p2);
console.log(p3);

reject

//Promise.reject 函数对象,非实例对象
//reject永远返回reject
Promise.reject = function(reason){
  return new Promise((resolve,reject)=>{
    reject(reason);
    console.error(reason);
  })
}
  
let p = Promise.reject('no ok');
let p2 = Promise.reject(new Promise((resolve,reject)=>{
    resolve('ok');
}))

console.log(p);
console.log(p2);

all

Promise.all([ … ]) 需要一个参数,是一个数组,通常由 Promise 实例组成。从 Promise. all([ … ]) 调用返回的 promise 会收到一个完成消息(代码片段中的 msg)。这是一个由所有传入 promise 的完成消息组成的数组,与指定的顺序一致(与完成顺序无关)。

严格说来, 传给 Promise.all([ … ]) 的数组中的值可以是 Promise、 thenable,甚至是立即值。就本质而言,列表中的每个值都会通过 Promise. resolve(…) 过滤,以确保要等待的是一个真正的 Promise,所以立即值会被规范化为为这个值构建的 Promise。如果数组是空的,主 Promise 就会立即完成

从 Promise.all([ … ]) 返回的主 promise 在且仅在所有的成员 promise 都完成后才会完 成。如果这些 promise 中有任何一个被拒绝的话,主 Promise.all([ … ])promise 就会立 即被拒绝,并丢弃来自其他所有 promise 的全部结果。

//Promise.all函数对象,非实例对象
Promise.all = function(promises){
  return new Promise((resolve,reject)=>{
    //遍历
    let count=0;
    let arr = [];
    for(let i=0;i<promises.length;i++){
      promises[i].then(v=>{
        //每个promise对象都成功,才能
        count++;
        arr[i] = v;
      },r=>{
        reject(r);
      })
      if(count==promises.length){
        resolve(arr);
      }
    }
  })
}

let p1 = new Promise((resolve,reject)=>{
  reject('error');
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('oh yeah');
let result = Promise.all([p1,p2,p3]);
console.log(result);

race

尽管 Promise.all([ … ]) 协调多个并发 Promise 的运行,并假定所有 Promise 都需要完成,但有时候你会想只响应"第一个跨过终点线的 Promise”,而抛弃其他 Promise。 这种模式传统上称为门闩,但在 Promise 中称为竞态:一旦有任何一个 Promise 决议为完成,Promise.race([ … ]) 就会完成;一旦有任何一个 Promise 决议为拒绝,它就会拒绝。

如果你传入了一个空数组,主 race([…]) Promise 永远不会决议,会挂住,而不是立即决议。

//Promise.race函数对象,非实例对象
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    //遍历
    for(let i=0;i<promises.length;i++){
      //赛跑
      promises[i].then(v=>{
        resolve(v);
      },r=>{
        reject(r);
      })
    }
  })
}

let p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('error');
    },1000)});
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('oh yeah');
let result = Promise.race([p1,p2,p3]);
console.log(result);

其他

  • none([ … ]) :这个模式类似于 all([ … ]),不过完成和拒绝的情况互换了。所有的 Promise 都要被 拒绝,即拒绝转化为完成值,反之亦然。
  • any([ … ]) :这个模式与 all([ … ]) 类似,但是会忽略拒绝,所以只需要完成一个而不是全部。
  • first([ … ]) :这个模式类似于与 any([ … ]) 的竞争,即只要第一个 Promise 完成,它就会忽略后续的任何拒绝和完成。如它的所有promise都拒绝的话,它不会拒绝只会挂住。
  • last([ … ]) :这个模式类似于 first([ … ]),但却是只有最后一个完成胜出

并发迭代

同步:forEach、every、some

异步:map

if (!Promise.map) { 
    Promise.map = function(vals,cb) { 
        // 一个等待所有map的promise的新promise 
        return Promise.all( 
            // 注:一般数组map(..)把值数组转换为 promise数组
            vals.map( function(val){ 
                // 用val异步map之后决议的新promise替换val 
                return new Promise( function(resolve){ 
                    cb( val, resolve ); 
                } ); 
            } ) 
        ); 
    }; 
} 

在这个 map(…) 实现中,不能发送异步拒绝信号,但如果在映射的回调 (cb(…))内出现同步的异常或错误,主 Promise.map(…) 返回的 promise 就会拒绝。

总体

function Promise(executor){
  //初始化
  this.PromiseState = 'padding';
  this.PromiseResult = null;
  //保存多个.then调用中的onResolved和onRejected
  this.callbacks = [];
  var self = this;

  //resolve
  function resolve(data){
    //只能调一次
    if(self.PromiseState !== 'padding') return;
    //注意this作用域
    self.PromiseState = 'fulfilled';
    self.PromiseResult = data;
    //异步callback回调执行
    self.callbacks.forEach(item =>{
      item.onResolved(data);
    })
  }

  //reject
  function reject(data){
    //只能调一次
    if(self.PromiseState !== 'padding') return;
    //注意this作用域
    self.PromiseState = 'rejected';
    self.PromiseResult = data;
    //异步callback回调执行
    self.callbacks.forEach(item => {
      item.onRejected(data);
    })
  }

  //解决throw
  try{
    executor(resolve,reject);
  }catch(e){
    reject(e);
  }

}

//then
Promise.prototype.then  = function(onResolved,onRejected){
  const self = this;
  //判断回调函数的参数
  if(typeof onRejected !== 'function'){
    onRejected = reason=>{
      // console.log(reason);
      throw reason;
    }
  }
  if(typeof onResolved !=='function'){
    onResolved = value=>{
      // console.log(value);
      return value;
    };
  }
  //调用回调函数,then函数返回一个promise对象
  return new Promise((resolve,reject)=>{
    //封装一个callback,参数type为onResolved或onRejected
    function callback(type){
      try{
        //获取这个回调函数的返回值
        let result = type(self.PromiseResult)
        //如果返回对象是promise
        if(result instanceof Promise){
          //那么将返回对象的结果赋值给res
          result.then(v=>{
            resolve(v);
          },r=>{
            reject(r);
          })
        }else{
          //如果返回对象不是promise直接将返回值赋给res【promise对象】的PromiseResult
          resolve(result);
        }        
      }catch(e){
        reject(e);
      }
    }
    //如果此时对象的PromiseState为fulfilled
    if(this.PromiseState=='fulfilled'){
      //then方法回调异步执行
      setTimeout(()=>{
        callback(onResolved);
      },0);
    }
    //如果此时对象的PromiseState为rejected
    if(this.PromiseState=='rejected'){
      //then方法回调异步执行
      setTimeout(()=>{
        callback(onRejected);
      },0);
    }
    //如果此时对象的PromiseState为padding
    //说明此时可能遇上了异步操作
    if(this.PromiseState=='padding'){
      //保存回调函数
      this.callbacks.push({
        onRejected:function(){
          callback(onRejected);
        },
        onResolved:function(){
          callback(onResolved);
        },
        })
    }
    }
  );
}

//catch
Promise.prototype.catch = function(onRejected){
  return this.then(undefined,onRejected);
}

//Promise.resolve 函数对象,非实例对象
//resolve视返回情况而定是resolve还是reject
Promise.resolve = function(value){
  //返回promise对象
  return new Promise((resolve,reject)=>{
    if(value instanceof Promise){
      value.then(v=>{
        resolve(v);
      },r=>{
        reject(r);
      })
    }else{
      resolve(value);
    }
  })
}

//Promise.reject 函数对象,非实例对象
//reject永远返回reject
Promise.reject = function(reason){
  return new Promise((resolve,reject)=>{
    reject(reason);
    console.error(reason);
  })
}

//Promise.all函数对象,非实例对象
Promise.all = function(promises){
  return new Promise((resolve,reject)=>{
    //遍历
    let count=0;
    let arr = [];
    for(let i=0;i<promises.length;i++){
      promises[i].then(v=>{
        //每个promise对象都成功,才能
        count++;
        arr[i] = v;
      },r=>{
        reject(r);
      })
      if(count==promises.length){
        resolve(arr);
      }
    }
  })
}

//Promise.race函数对象,非实例对象
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    //遍历
    for(let i=0;i<promises.length;i++){
      //赛跑
      promises[i].then(v=>{
        resolve(v);
      },r=>{
        reject(r);
      })
    }
  })
}

class版本

class Promise{
  //构造方法
  constructor(executor){
    //初始化
    this.PromiseState = 'padding';
    this.PromiseResult = null;
    //保存多个.then调用中的onResolved和onRejected
    this.callbacks = [];
    var self = this;

    //resolve
    function resolve(data){
      //只能调一次
      if(self.PromiseState !== 'padding') return;
      //注意this作用域
      self.PromiseState = 'fulfilled';
      self.PromiseResult = data;
      //异步callback回调执行
      self.callbacks.forEach(item =>{
        item.onResolved(data);
      })
    }

    //reject
    function reject(data){
      //只能调一次
      if(self.PromiseState !== 'padding') return;
      //注意this作用域
      self.PromiseState = 'rejected';
      self.PromiseResult = data;
      //异步callback回调执行
      self.callbacks.forEach(item => {
        item.onRejected(data);
      })
    }

    //解决throw
    try{
      executor(resolve,reject);
    }catch(e){
      reject(e);
    }
  }

  //then 方法封装
  then(onResolved,onRejected){
    const self = this;
    //判断回调函数的参数
    if(typeof onRejected !== 'function'){
      onRejected = reason=>{
        // console.log(reason);
        throw reason;
      }
    }
    if(typeof onResolved !=='function'){
      onResolved = value=>{
        // console.log(value);
        return value;
      };
    }
    //调用回调函数,then函数返回一个promise对象
    return new Promise((resolve,reject)=>{
      //封装一个callback,参数type为onResolved或onRejected
      function callback(type){
        try{
          //获取这个回调函数的返回值
          let result = type(self.PromiseResult)
          //如果返回对象是promise
          if(result instanceof Promise){
            //那么将返回对象的结果赋值给res
            result.then(v=>{
              resolve(v);
            },r=>{
              reject(r);
            })
          }else{
            //如果返回对象不是promise直接将返回值赋给res【promise对象】的PromiseResult
            resolve(result);
          }        
        }catch(e){
          reject(e);
        }
      }
      //如果此时对象的PromiseState为fulfilled
      if(this.PromiseState=='fulfilled'){
        //then方法回调异步执行
        setTimeout(()=>{
          callback(onResolved);
        },0);
      }
      //如果此时对象的PromiseState为rejected
      if(this.PromiseState=='rejected'){
        //then方法回调异步执行
        setTimeout(()=>{
          callback(onRejected);
        },0);
      }
      //如果此时对象的PromiseState为padding
      //说明此时可能遇上了异步操作
      if(this.PromiseState=='padding'){
        //保存回调函数
        this.callbacks.push({
          onRejected:function(){
            callback(onRejected);
          },
          onResolved:function(){
            callback(onResolved);
          },
          })
      }
      }
    );
  }

  //catch方法封装
  catch(onRejected){
    return this.then(undefined,onRejected);
  }

  Promise.resolve 函数对象,非实例对象
  //resolve视返回情况而定是resolve还是reject
  static resolve(value){
    //返回promise对象
    return new Promise((resolve,reject)=>{
      if(value instanceof Promise){
        value.then(v=>{
          resolve(v);
        },r=>{
          reject(r);
        })
      }else{
        resolve(value);
      }
    })
  }

  //Promise.reject 函数对象,非实例对象
  //reject永远返回reject
  static reject(reason){
    return new Promise((resolve,reject)=>{
      reject(reason);
      console.error(reason);
    })
  }
    
  //Promise.all函数对象,非实例对象
  static all(promises){
    return new Promise((resolve,reject)=>{
      //遍历
      let count=0;
      let arr = [];
      for(let i=0;i<promises.length;i++){
        promises[i].then(v=>{
          //每个promise对象都成功,才能
          count++;
          arr[i] = v;
        },r=>{
          reject(r);
        })
        if(count==promises.length){
          resolve(arr);
        }
      }
    })
  }
  
  //Promise.race函数对象,非实例对象
  static race(promises){
    return new Promise((resolve,reject)=>{
      //遍历
      for(let i=0;i<promises.length;i++){
        //赛跑
        promises[i].then(v=>{
          resolve(v);
        },r=>{
          reject(r);
        })
      }
    })
  }
}

async函数

函数的返回值是promise对象,async的意思是当前这个异步函数与同一作用域下的程序是异步的关系

promise对象的结果由async函数执行的返回值决定:

  • 如果返回值是一个非promise类型的数据,一般返回fulfiiled
  • 如果返回的是一个promise对象,结果看promise的结果状态是fulfilled还是rejected
  • 抛出异常,返回promise对象,状态为rejected

await表达式

await 是一个操作符,其等待一个Promise对象产出结果的操作手段,它的功能是暂停async函数的执行,等待Promise处理后的结果。假如Promise处理的结果是rejected,会抛出异常。async函数是通过一个隐式的Promise返回pendding状态,所以这个状态其实不重要,Promise处理的结果比较重要。

await右侧的表达式一般为promise对象,但也可以是其它的值

  • 如果表达式是promise对象, await返回的是promise成功的值
  • 如果表达式是其它值,直接将此值作为await的返回值
  • 如果await 的promise 失败了,就会抛出异常,需要通过try…catch捕获处理

await必须写在async.函数中,但async函数中可以没有await

async function main(){
    let p = new Promise((resolve,reject)=>{
        reject('error');
    })
    try{
        let res3  = await p;
    }catch(e){
        console.log(e);
    }
}

小栗子

回调地狱

const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);
async function main(){
    try{
        let data1 = await mineReadFile("./1.html");
        let data2 = await mineReadFile("./2.html");
        let data3 = await mineReadFile("./3.html");
        console.log(data1+data2+data3);  
    }catch(e){
        console.log(e);
    }
}

promise局限性

顺序错误处理

Promise 链中的错误很容易被 无意中默默忽略掉。

由于一个 Promise 链仅仅是连接到一起的成员 Promise,没有把整个链标识为一个个体的实体,这意味着没有外部方法可以用于观察可能发生的错误。

p.catch( handleErrors ); 如果链中的任何一个步骤事实上进行了自身的错误处理(可能以隐藏或抽象的不可见的方式),那handleErrors(…) 就不会得到通知。promise其中一个局限性就是只能捕获到”未处理“的错误。若错误被程序自身处理了,那catch将收不到通知。

单一值

Promise 只能有一个完成值或一个拒绝理由。

解决办法一:分裂值

function foo(bar,baz) { 
    var x = bar * baz; 
    // 返回两个promise
    return [ 
        Promise.resolve( x ), 
        getY( x ) 
    ]; 
} 
Promise.all( 
    foo( 10, 20 ) 
) 
    .then( function(msgs){ 
    var x = msgs[0]; 
    var y = msgs[1]; 
    console.log( x, y ); 
} );

解决办法二:展开/传递参数

Promise.all( 
    foo( 10, 20 ) 
) 
    .then( Function.apply.bind( 
    function(x,y){ 
        console.log( x, y ); // 200 599 
    }, 
    null 
) ); 

Promise.all( 
    foo( 10, 20 ) 
) 
    .then( function(msgs){ 
    var [x,y] = msgs; 
    console.log( x, y ); // 200 599 
} ); 

//ES6 提供了数组参数解构形式
//我们符合了“每个 Promise 一个值”的理念,并且又将重复样板代码量保持在了最小!
Promise.all( 
    foo( 10, 20 ) 
) 
    .then( function([x,y]){ 
    console.log( x, y ); // 200 599 
} ); 

调用代码来决定如何安排这两个promise,而不是把这种细节放在foo内部抽象。

单决议

Promise 只能被决议一次(完成或拒绝)。

设想这样一个场景:你可能要启动一系列异步步骤以响应某种可能多次发生的激励(就像 是事件),比如按钮点击。 这样可能不会按照你的期望工作:

// click(..)把"click"事件绑定到一个DOM元素
// request(..)是前面定义的支持Promise的Ajax 
var p = new Promise( function(resolve,reject){ 
    click( "#mybtn", resolve ); 
} ); 
p.then( function(evt){ 
    var btnID = evt.currentTarget.id; 
    return request( "http://some.url.1/?id=" + btnID ); 
} ) 
    .then( function(text){ 
    console.log( text ); 
} ); 

只有在你的应用只需要响应按钮点击一次的情况下,这种方式才能工作。如果这个按钮被 点击了第二次的话,promise p 已经决议,因此第二个 resolve(…) 调用就会被忽略。 因此,你可能需要转化这个范例,为每个事件的发生创建一整个新的 Promise 链:

click( "#mybtn", function(evt){ 
    var btnID = evt.currentTarget.id; 
    request( "http://some.url.1/?id=" + btnID ) 
        .then( function(text){ 
        console.log( text ); 
    } ); 
} ); 

由于需要在事件处理函数中定义整个 Promise 链,这很丑陋。除此之外,这个设计在某种程度上破坏了关注点与功能分离(SoC)的思想。你很可能想要把事件处理函数的定义和 对事件的响应(那个 Promise 链)的定义放在代码中的不同位置。如果没有辅助机制的话, 在这种模式下很难这样实现。

惯性

要在你自己的代码中开始使用 Promise 的话,一个具体的障碍是,现存的所有代码都还不 理解 Promise。如果你已经有大量的基于回调的代码,那么保持编码风格不变要简单得多。Promise 提供了一种不同的范式,因此,编码方式的改变程度从某处的个别差异到某种情 况下的截然不同都有可能。你需要刻意的改变,因为 Promise 不会从目前的编码方式中自 然而然地衍生出来

我们绝对需要一个支持 Promise 而不是基于回调的 Ajax 工具,可以称之为 request(…)。你可以实现自己的版本,就像我们所做的一样。但是,如果不得不为每个基 于回调的工具手工定义支持 Promise 的封装,这样的开销会让你不太可能选择支持 Promise 的重构。 Promise 没有为这个局限性直接提供答案。多数 Promise 库确实提供辅助工具,但即使没有 库,也可以考虑如下的辅助工具:

// polyfill安全的guard检查
if (!Promise.wrap) { 
    Promise.wrap = function(fn) { 
        return function() { 
            var args = [].slice.call( arguments ); 
            return new Promise( function(resolve,reject){ 
                fn.apply( 
                    null, 
                    args.concat( function(err,v){ 
                        if (err) { 
                            reject( err ); 
                        } 
                        else { 
                            resolve( v ); 
                        } 
                    } ) 
                ); 
            } ); 
        }; 
    }; 
} 

var request = Promise.wrap( ajax ); 
request( "http://some.url.1/" ) 
    .then( .. ) 
    ..

Promise.wrap(…) 并不产出 Promise。它产出的是一个将产生 Promise 的函数。在某种意义 上,产生 Promise 的函数可以看作是一个 Promise 工厂。我提议将其命名为“promisory” (“Promise”+“factory”)。 把需要回调的函数封装为支持 Promise 的函数,这个动作有时被称为“提升”或“Promise 工厂化”。但是,对于得到的结果函数来说,除了“被提升函数”似乎就没有什么标准术 语可称呼了。所以我更喜欢“promisory”这个词,我认为它的描述更准确。

我们需要为 ajax(…) 和 foo(…) 都构造一个 promisory:

// 为ajax(..)构造一个promisory
var request = Promise.wrap( ajax ); 
// 重构foo(..),但使其外部成为基于外部回调的,
// 与目前代码的其他部分保持通用
// ——只在内部使用 request(..)的promise 
function foo(x,y,cb) { 
    request( 
        "http://some.url.1/?x=" + x + "&y=" + y 
    ) 
        .then( 
        function fulfilled(text){ 
            cb( null, text ); 
        }, 
        cb 
    ); 
} 
// 现在,为了这段代码的目的,为foo(..)构造一个 promisory
var betterFoo = Promise.wrap( foo ); 
// 并使用这个promisory 
betterFoo( 11, 31 ) 
    .then( 
    function fulfilled(text){ 
        console.log( text ); 
    }, 
    function rejected(err){ 
        console.error( err ); 
    } 
); 

无法取消的promise

一旦创建了一个 Promise 并为其注册了完成和 / 或拒绝处理函数,如果出现某种情况使得 这个任务悬而未决的话,你也没有办法从外部停止它的进程。

很多 Promise 抽象库提供了工具来取消 Promise,但这个思路很可怕!很多 开发者希望 Promise 的原生设计就具有外部取消功能,但问题是,这可能 会使 Promise 的一个消费者或观察者影响其他消费者查看这个 Promise。这 违背了未来值的可信任性(外部不变性)。

var p = foo( 42 ); 
Promise.race( [ 
    p, 
    timeoutPromise( 3000 ) 
] ) 
    .then( 
    doSomething, 
    handleError 
); 
p.then( function(){ 
    // 即使在超时的情况下也会发生
} );

这个“超时”相对于 promise p 是外部的,所以 p 本身还会继续运行。

一种选择是侵入式地定义你自己的决议回调:

var OK = true; 
var p = foo( 42 ); 
Promise.race( [ 
    p, 
    timeoutPromise( 3000 ) 
    .catch( function(err){ 
        OK = false; 
        throw err; 
    } ) 
] ) 
    .then( 
    doSomething, 
    handleError 
); 
p.then( function(){ 
    if (OK) { 
        // 只在没有超时情况下才会发生 :)
    } 
} ); 

单独的一个 Promise 并不是一个真正的流程控制机制(至少不是很有意义),这正是取消所 涉及的层次(流程控制)。这就是为什么 Promise 取消总是让人感觉很别扭。相比之下,集合在一起的 Promise 构成的链,我喜欢称之为一个“序列”,就是一个流程控 制的表达,因此将取消定义在这个抽象层次上是合适的。 单独的promise应当是原子的,只可resolve或reject,不应该被中断。但由一系列promise组成的序列可视为同一组任务,类似于流程控制,在此任务上对中断进行控制是合理的。

性能

更多的工作,更多的保护。这些意味着 Promise 与不可信任的裸回调相比会更慢一些。Promise 稍慢一些,但是作为交换,你得到的是大量内建的可信任性、对 Zalgo 的避免以及 可组合性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值