是 promise chain 解决了回调地狱,而不是只靠 promise

1,754 阅读7分钟

前言

最近因为对 promise 的状态依赖理解有偏差,导致在开发过程中花费了3个小时的时间才发现错误。感觉浪费时间了,所以结合标准及实践结果对 promise 的状态依赖做了一个总结。

问题代码大致是这样的:

// 假设promise为请求错误回调
 let promise = new Promise((resolve, reject) => {
    reject('400'); 
 });
 
 // 统一的响应拦截处理
 promise.then((res) => {
    console.log('promise injector resolved', res) 
 }, (err) => {
    console.log('promise injector rejected', err) 
 })
 // 请求调用处的业务响应处理
 .then((res) => {
    console.log('promise resolved', res) 
 }, (err) => {
    console.log('promise rejected', err) 
 })
 

上面代码中表现的场景是,错误请求经过请求响应拦截器的统一处理后,业务逻辑本身再根据请求状态来进行一些其他的处理。本来按照我对promise的理解,这样是没有问题的。实际上这里的问题是,业务中的请求响应处理永远只会走成功回调,而不会走失败回调。因为在进行统一的响应拦截处理的时候,就已经丢失了 promise 的状态了。

promise 的三种状态

promise共有是三种状态:

  • Pending - promise 初始化状态
  • Fulfilled - 成功
  • Rejected - 失败

这三种状态是 Promises/A+ 中描述状态的术语

当我们创建一个 promise 时,它的状态是 Pending,然后随着异步任务的执行,它的状态一定会变成 Fulfilled 和 Rejected 中的一种,且它的状态不会再发生任何变化(这一点很重要,后面捋清楚 promise.then() 的返回值就靠这个特性了)。

promise.then() 返回的是一个新的 promise

先来一代码:

let promise = Promise.resolve('test');

let thenPromise = promise.then( res =>{
    console.log(res)
});
console.log(promise === thenPromise);
console.log(thenPromise);

运行结果如下:

从运行结果中我们不难得出两点:

  1. promise.then() 返回的 promise 和调用 .then() 的 promise 不是同一个
  2. promise.then() 返回的 promise 状态为 resolved,对应本文中描述 promise 状态的 Fulfilled。

promise.then() 返回的是一个新的 Promise 的实例对象,而 promise 是能够能表示状态的,这样就可以形成一条状态的依赖链。也就能将多个任务之间的依赖及执行顺序表示出来了,从而将异步任务的回调嵌套转化成为一条扁平的链条。

promise chain

promise chain 说白了就是个 promise 的调用链,代码形式大致如下:

var promise1 = Promise.reject('test');
promise1.then(function(res) {
    console.log('fulfilled1', res)
}, function(err) {
    console.log('rejected1', err)
    return err;
}).then(function(res) {
    console.log('fulfilled2', res)
}, function(err) {
    console.log('rejected2', err)
})

这样的链式调用方式,很好的将多个存在依赖关系的异步任务,将难看回调嵌套转化成了一条更易于理解的扁平链条。从而解决了所谓回调地狱的问题。

promise.then() 回调函数的两种返回值

当我们创建了一个 promise 之后,就可以使用 promise.then() 来注册,Fulfilled 和 Rejected 状态对应的执行函数了。类似下面这样:

var promise = new Promise((resolve, reject)=>{
    resolve('ok');
});
promise.then((res)=>{
    console.log('fulfilled', res)
}, (err)=>{
    console.log('rejected', err)
})

promise.then() 注册的回调函数可以返回不同的值,分为以下两种:

  • 返回除Promise 实例对象以外的任何值
  • Promise 实例对象

promise.then() 回调函数返回值对 promise chain 的影响

promise.then() 回调函数的返回值决定了其返回的 promise,大致分为以下两种情形:

  1. 回调函数返回除 Promise 实例对象之外的任何值,决定返回的 promise 注册的回调函数的实参
  2. 回调函数返回 Promise 实例对象,决定 promise.then() 返回的 promise 的状态和其注册的回调函数的实参

注意: 这里的回调函数指的是 promise.then() 注册的回调函数,且是指的是被调用的函数,与是成功回调还是失败回调无关

被执行的回调函数返回除 Promise 实例对象之外的任何值

当 promise.then() 注册的回调函数返回除promise以外的值时,返回的值会被当做 promise.then() 返回的新的 promise 使用 .then() 注册的回调函数的传入值。

当回调函数未返回值时,考虑下面代码:

let promise = Promise.resolve();
promise.then(()=>{
    console.log('promise resolved')
}, ()=>{
    console.log('promise rejected')
})
.then((value)=>{
    console.log('promise resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise rejected1')
    console.log(err)
})

// promise resolved
// promise resolved1
// undefined

let promise1 = Promise.reject();
promise1.then(()=>{
    console.log('promise1 resolved')
}, ()=>{
    console.log('promise1 rejected')
})
.then((value)=>{
    console.log('promise1 resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise1 rejected1')
    console.log(err)
})

// promise1 rejected
// promise1 resolved1
// undefined

运行结果如上,为了方便阅读结果,我们把打印顺序调换一下,整理如下

promise 注册的回调运行了成功回调,而后返回了一个成功状态的promise,这个返回的 promise 注册的回调运行了成功回调,回调函数的实参是 undefined;promise1 注册的回调运行了失败回调,而后同样返回了一个成功状态的 promise ,这个返回的 promise 注册的回调运行了成功回调,回调函数的实参是 undefined

js 函数执行后默认返回值为undefined

再来看看返回其他值的情况:

let promise = Promise.resolve();
promise.then(()=>{
    console.log('promise resolved')
    return 'value'
}, ()=>{
    console.log('promise rejected')
    return 'err'
})
.then((value)=>{
    console.log('promise resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise rejected1')
    console.log(err)
})
// promise resolved
// promise resolved1
// value

let promise1 = Promise.reject();
promise1.then(()=>{
    console.log('promise1 resolved')
    return 'value'
}, ()=>{
    console.log('promise1 rejected')
    return 'err'
})
.then((value)=>{
    console.log('promise1 resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise1 rejected1')
    console.log(err)
})
// promise1 rejected
// promise1 resolved1
// err

promise.then() 注册的回调函数,运行的是成功回调,且返回了字符串 “value”,promise.then() 函数返回了一个成功状态的 promise, 返回的 promise 注册的回调函数运行了成功回调,回调函数的实参是字符串“value”;promise1.then() 注册的回调函数,运行的是失败回调,且返回了字符串 “err”,promise1.then() 函数返回了一个成功状态的 promise, 返回的 promise 注册的回调函数运行了成功回调,回调函数的实参是字符串“err”

从上面两段示例代码及其运行结果,不难得出如下结论:

  1. 当 promise.then() 注册的回调函数返回的不是一个 Promise 示例对象时(代码只示例了返回字符串和undefined的情况,其他情况有兴趣的同学可以验证下返回其他类型的值),promise.then() 返回的是一个 Fulfilled 状态的新的 Promise 实例
  2. 这个成功回调的实参就是上一个运行的回调函数的返回值

被执行的回调函数返回 Promise 实例对象

当 promise.then() 注册的被运行的回调函数返回一个 Promise 实例的时候,回调返回的 Promise 实例的最终的状态会决定 promise.then() 函数返回的这个新的 promise 的状态。且promise.then() 函数返回的 promise 注册的回调函数的实参是由 Promise 实例对象 resolve 或 reject 传递的参数决定的。

考虑如下代码:

let promiseValue = Promise.reject('promiseValue err');

let promise = Promise.resolve();
let promiseThen = promise.then(()=>{
    console.log('promise resolved');
    return promiseValue;
})
promiseThen.then((res)=>{
    console.log('promise resolved1');
    console.log(res)
}, (err)=>{
    console.log('promise rejected1');
    console.log(err);
})

// promise resolved
// promise rejected1
// promiseValue err

console.log(promiseThen === promiseValue)

// false

promise 执行成功状态回调,回调函数返回失败状态的 Promise 实例对象 promiseValue,且其 rejecte 传入的值是字符串 “promiseValue err”。因为 promiseValue 的状态是 rejected 所以 promise.then() 返回的新的 Promise 实例(promiseThen === promiseValue 的结果是 false 证明promise.then()返回的)promiseThen 的状态是 rejected,promiseThen 注册的失败回调被调用,打印的实参为字符串“promiseValue err”。

promise.then() 注册的被执行的回调函数返回 Promise 实例对象时:

  1. promise.then() 返回一个新的 Promise 实例对象,这个实例对象会等待回调函数返回 Promise 实例对象的状态变成Fulfilled 或 Rejected 中的一种后返回,且状态和回调函数返回 Promise 实例对象的状态一致
  2. promise.then() 返回的 Promise 实例对象,注册的回调函数被执行的时候接受的实参是由回调函数返回 Promise 实例对象传递的结果决定

总结

正是因为 promise.then() 返回的是一个新的 Promise 实例,且这个实例可以向后传递上一个依赖 promise 的异步任务的执行状态和其要传递的数据。这样的链式调用机制,解决了存在依赖关系的异步任务只能在回调函数中不断嵌套的最后导致代码难以维护的问题。