Promise深入探索

1,453 阅读5分钟

Promis.resolve()

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

假设我们要调用一个工具 foo(..) ,且并不确定得到的返回值是否是一个可信任的行为良好的 Promise,但我们可以知道它至少是一个 thenable。Promise.resolve(..) 提供了可信任的 Promise 封装工具

// 不要只是这么做:
foo( 42 )
.then( function(v){
    console.log( v );
} );

// 而要这么做:
Promise.resolve( foo( 42 ) )
.then( function(v){
    console.log( v );
} );

对于用 Promise.resolve(..) 为所有函数的返回值(不管是不是 thenable)都封装一层。另一个好处是,这样做很容易把函数调用规范为定义良好的异步任务。如果 foo(42) 有时会返回一个立即值,有时会返回 Promise,那么 Promise.resolve( foo(42) ) 就能够保证总会返回一个 Promise 结果

默认错误处理函数

如果一个promise链中的上一级promise抛出一个错误,但是下一级promise并没有配置错误处理函数,promise会有一个默认处理函数来处理这个错误,即吧这个错误重新抛出给下一级promise的错误处理函数。

如果一直找到最后一级的promise都没有错误处理函数,那么promise会在控制台打出一个错误信息,并且这个错误是有可能中断nodejs进程的。

DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

(在浏览器环境貌似并不会中断进程)

Promise链

  • 调用promise的then方法,会自创建一个新的Promise,并且决议的值就是then中resolve/reject函数的返回值;
  • 如果resolve/reject函数返回的是一个Promise,那么会等待这个Promise的决议结果,并将决议结果作为当前then方法生成的Promise的决议值;
var a = new Promise(function(resolve, reject) {
  resolve(10)
}).then(
  res => {
    return b
  }
).then(res => {
  console.log('1err:' + res)
  return res
}).then(res => {
  console.log('1res:' + res)
  return res
}, err => {
  console.log('2err:' + err)
})

var b = new Promise(function(resolve, reject) {
  resolve(123123123123)
})

Promise构造器中两个回调函数

reject很好理解就是处理错误,那么resolve为什么不用fulfill呢?貌似应该是处理成功请求?实际上resolve的意思是决议,也就是可能是成功的结果也可能是失败的结果;比如所给resolve传入thenable或者一个真正的Promise,这个时候决议的结果取决于返回值的决议结果,所以说resolve返回的有可能是成功也有可能是失败;

var rejectedPr = new Promise( function(resolve,reject){
    // 用一个被拒绝的promise完成这个promise
    resolve( Promise.reject( "Oops" ) );
} );

rejectedPr.then(
    function fulfilled(){
        // 永远不会到达这里
    },
    function rejected(err){
        console.log( err ); // "Oops"
    }
);

前面提到的 reject(..) 不会像 resolve(..) 一样进行展开。如果向 reject(..) 传入一个 Promise/thenable 值,它会把这个值原封不动地设置为拒绝理由。后续的拒绝处理函数接收到的是你实际传给 reject(..) 的那个 Promise/thenable,而不是其底层的立即值。

回调函数简介

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

而then方法的两个回调函数应该使用onFulfilles/fulfilled和onRejected/rejected因为对于then来说第一个回调只能处理正确的结果。

(fulfilled the promis 兑现承诺)

Promise错误处理函数的细节

通常我们有两种写法来捕获错误:

var a = new promise(function(resolve, reject){})

// 在then方法的内部第二个回调捕获
a.then(res => {
    
}, err => {
    
})

// 在catch方法中捕获
a.then(res => {}).catch(err => {})

那么这两种方法有什么不同? 实际上第二种方法是包含第一种方法的,只不过then方法中的第二个回调被默认执行了,而默认的错误处理回调只是简单的抛出错误,然后就会被catch方法捕获; 这里就有一个问题,如果a的决议是resolve,那么就会调用then的第一个成功回调,如果这个成功回调中的具体代码中发生了错误,第一种方法是无法捕获的,而第二种方法是可以捕获的。

var a = new promise(function(resolve, reject){
    resolve(12)
})

// 在then方法的内部第二个回调捕获
a.then(res => {
    aaa() // 这里aaa是不存在的会发生错误
}, err => {
    console.log(err) // 不会走这里,所以错误不会被捕获
})

// 在catch方法中捕获
a.then(res => {
    aaa() // 这里aaa是不存在的会发生错误
}).catch(err => {
    console.log(err) // 这里会捕获then方法中的所有错误,自然也能捕获aaa导致的错误
})

Promise.race()的应用场景

实际上Promise.race([p1, p1, p3])这种模式在程序设计中通常是一种被称为竞态的bug,但是它还是有一定的应用场景:

  1. 请求超时处理
var p1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(2222)
  }, 2000)
})
// 定义一个超时函数,如果p1超过这个时间还未决议,就认为这个请求超时了。
var timeoutPromise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('timeout')
  }, 1000)
})

Promise.race([p1, timeoutPromise]).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

有时候我们可能会想,Promsie.race()中那些别丢弃(比赛中失败)的Promise哪里去了?当然最后会被垃圾回收,但是,Promsie不能别停止,它们依旧会完成自己的任务直到决议,目前来看只是被默默的丢弃了,像空气一样,或许在以后的规范中会添加类似与finally之类的回调函数用于处理这些被遗弃的孩子。