异步三部曲之Promise

开始的异步处理方式

getDataA(params, function(dataA){
      // ...
    getDataB(params, function(dataB){
        // ...
        getDatac(params, function(dataC){
          // ...
        })
    })
})

开始的异步处理方式是这种通过callback进行处理,一步步去处理,当需要处理N步时就要有N个回调函数,代码就会纵向发展,同时使阅读性大大降低。
这种方式有个名字叫:地狱回调!很好理解,就像地狱一样,一层一层的进行调用。

Promise

基本用法

// 创建一个Promise对象
const promise = new Promise(function(resolve, reject) {
  // 你可以在里面执行你的异步操作,例如请求数据
    getDataA(params, function(dataA) {
      if(dataA) {
        // 改变状态为成功并传入数据
        resolve(dataA)
      }
      // 改变状态为失败并传入错误信息
      reject(new Error(dataA))
    })
})

promise.then(function(dataA) {
  // 成功后的回调
}, function(err) {
  // 失败后的回调
})

Promise对象接收一个函数作为参数,这个函数拥有两个参数,代表成功(resolve)和代表失败(reject)的函数
这里暴躁老哥就要说了:这不还是要回调,只不过包了一层,完全是多此一举!
大佬您别急,请继续往下看~~

Promise执行

console.log('Hello')
const promise = new Promise(function(resolve, reject) {
  console.log('Hi Promise')
  setTimeout(function() {
    console.log(1)
    resolve('成功了')
  }, 1000)
  setTimeout(function() {
    console.log(2)
    reject('失败了')
  }, 2000)
})
promise.then(function(res) {
  console.log(res);
}, function(err) {
  console.log(err);
})
console.log('Bay')

// Hello
// Hi Promise
// Bay
// 1
// 成功了
// 2

可以看到promise在创建时就会调用,并且会告诉异步的操作:哎,你先处理着,完了告诉我结果,我先去忙别的了。
Promise的特点:
1.Promise创建时就会执行
2.Promise会有三个状态,处理中、成功、失败
3.即使出了结果,里面的异步操作并不会停止!
4.虽然出了结果依旧会执行完所有异步,但是以第一个结果为准
暴躁老哥一拍桌子!好家伙,拿到结果就跑,后面说啥不管了。

Promise.prototype.then

let num = 0;

function nextF(data) {
  num +=1;
  console.log(num, data)
  return data;
}

const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('成功了')
    }, 1000)
})

promise
.then(nextF)
.then(nextF)
.then(nextF)

// 1 成功了
// 2 成功了
// 3 成功了

这里我定义了一个函数nextF,和一个变量numnextF函数每次执行会使num+1并打印后把参数return。然后我把它传给promise.then,通过打印出的结果可以看出,then返回的是一个新的promise,并且默认是Fulfilled状态,所以then可以链式调用。

Promise.prototype.catch

let num = 0;

function nextF(data) {
  num +=1;
  console.log(num, data)
  return data;
}

const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject('失败了')
    }, 1000)
})

promise
.catch(nextF)
.catch(nextF)
.catch(nextF)

// 1 失败了

这里改写了一下上面的例子,把promise的状态改为Rejected,发现catch方法只执行了一次,同样连续调用不会报错。所以catch方法和then方法一样会返回一个新的promise,并且间接证明了thencatch返回的新promise都是默认成功状态。

同时catch方法还有一个作用,捕获promise抛出的错误。
暴躁老哥:你这也证明不了返回的新promise默认是Fulfilled状态呀!那您接着往下看

let num = 0;

function nextF(data) {
  num +=1;
  console.log(num, data)
  return data;
}

const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('成功了')
    }, 1000)
})

promise
.then(nextF)
.then(function(data) {
  num +=1;
  console.log(num, data)
  throw new Error('我喜欢红色')
})
.then(nextF)
.then(nextF)
.catch(nextF)
.then(nextF)

// 1 成功了
// 2 成功了
// 3 Error: 我喜欢红色
// 4 Error: 我喜欢红色

这里在第二个then方法中抛出一个错误,结果打印了两个成功了、两个失败了。为啥两个失败了,因为catch捕获了错误并返回,而返回的是一个新的promise,而这个新的promise默认Fulfilled状态,导致最后的then方法运行了,打印出了第二个失败了。

这里也证明了一个很神奇的功能,那就是当promise抛出错误时,会跳过后面的then直到被catch捕获。promise出现Rejected时也一样。
所以reject的作用是抛出一个异常!暴躁老哥先坐下,我知道你要说什么。当promiseRejected状态时,并且后面没有进行捕获时,控制台会打印出一个Error!

Promise.prototype.finally

new Promise(function(resolve, reject) {
  resolve('成功了')
})
.then(function(res) {
  console.log(res)
  return res
})
.catch(function(err) {
  console.log(err);
  return err
})
.finally(function(data) {
  console.log('finally', data);
  return data
})
.then(function(res) {
  console.log(res)
  return res
})

// 成功了
// finally undefined
// 成功了

// 另一个例子

new Promise(function(resolve, reject) {
  throw new Error('红扑扑')
})
.then(function(res) {
  console.log(res)
  return res
})
.catch(function(err) {
  console.log(err);
  return err
})
.finally(function(data) {
  console.log('finally', data);
  return data
})
.then(function(res) {
  console.log(res)
  return res
})

// Error: 红扑扑
// finally undefined
// Error: 红扑扑

finallythencatch方法一样返回一个新的promise并且默认Fulfilled状态。但是他接收的回调函数没有任何参数,同样返回值不会影响后面的行为,并且无论什么状态都会执行。所以一般只会用来在处理promise完毕后的善后工作

Promise.resolve

const p = Promise.resolve('a')

p.then(function(data) {
  console.log(data);
})

// a

// 打印p会得到一个Promise实例
// 所以

Promise.resolve('a')
// 等价于
new Promise(function(resolve) {
  resolve('a')
})

1.参数是一个 Promise 实例

const p = Promise.resolve(
  new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('失败了')
    })
  })
)

p.then(function(data) {
  console.log(data);
})
.catch(function(err) {
  console.log(err)
})

// 失败了

会执行这个Promise,所以原本Promise.resolve代表Fulfilled本应会执行then,但是并未走then而是直接执行了catch

2.参数是一个thenable对象
什么是thenable对象?
thenable对象就是拥有then方法对象

let thenable = {
  a: 1,
  then: function(resolve, reject) {
    console.log(this.a)
    reject(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
  console.log('then', value)
})
.catch(function(err) {
  console.log('catch', err)
});

// 1
// catch 42

会把这个thenable对象转换成Promise对象并执行then方法,所以跳过了p1.then执行了p1.catch

3.不传参数

let p1 = Promise.resolve();
p1.then(function (value) {
  console.log('then', value)
})
.catch(function(err) {
  console.log('catch', err)
});

// then undefined

Promise.resolve允许不传参数,但是会直接返回一个Fulfilled状态的Promise

Promise.reject
Promise.reject的用法等同于Promise.resolve

let p1 = Promise.reject();
p1.then(function (value) {
  console.log('then', value)
})
.catch(function(err) {
  console.log('catch', err)
});

// catch undefined

区别是resolve传入的如果是一个promise对象或者thenable对象会执行,以得到的结果为准,如果不是则转换为Promise对象并执行resolve,参数就是传入的参数。而reject无论传入什么,都会返回一个rejected状态的Promise,而传入的参数就是失败或者说错误的理由

批量处理Promise对象

const p1 = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve('p1')
  }, 1000)
})
const p2 = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve('p2')
  }, 2000)
})

const p3 = new Promise(function(resolve, reject){
  setTimeout(function() {
    reject('p3')
  }, 500)
})

const p4 = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve('p4')
  }, 0)
})

这里创建p1p2p3p4四个Promise实例

Promise.all

Promise.all([p1, p2, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})
// success ["p1", "p2", "p4"]

Promise.all([p1, p3, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})

// error p3

Promise.all接受一个数组作为参数,数组的项如果是Promise对象就会直接执行,如果不是就会默认执行上面说到的Promise.resolve进行转换,并返回一个新的Promise对象,新的Promise的状态取决于传入的一系列Promise对象:
1.p1、p2、p3的状态都为Fulfilled,这个Promise对象的状态就是Fulfilled,传入then方法是一个数组对应p1、p2、p3
2.p1、p2、p3在执行中有一个出现Rejected,就会直接调用catch,错误信息即最先Rejected的信息

Promise.race

Promise.race([p1, p2, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})
// success p4

Promise.race([p1, p3])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})

// error p3

Promise.race的用法等同于Promise.all,区别于Promise.race的状态是赛跑原则。取决于得到的第一个状态

Promise.allSettled

Promise.allSettled([p1, p2, p3, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})
/* success [
  {
    status: "fulfilled",
    value: "p1"
  },
  {
    status: "fulfilled",
    value: "p2"
  },
  {
    status: "rejected",
    value: "p3"
  },
  {
    status: "fulfilled",
    value: "p4"
  }
]
*/

Promise.allSettled的用法等同于Promise.all,区别于Promise.allSettled不会出现rejected状态,它会等所有的Promise执行完之后一起返回

Promise.any

Promise.any([p1, p2, p3, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})

// success p4

Promise.any的用法和Promise.race一致,区别于正好相反Promise.any是当参数实例全部rejected时才会是rejected,当参数实例中出现一个Fulfilled它的状态就会是fulfilled,传入then的参数就是这个参数实例的结果

Promise处理异步

最初的例子用Promise改写

new Promise(function(resolve, reject) {
  getDataA(params, function(data) {
    if(data){
      resolve(data)
    }else {
      reject(new Error(data))
    }
  })
})
.then(function(dataA) {
  return new Promise(function(resolve, reject) {
    getDataB(params, function(data) {
      if(data){
        resolve(data)
      }else {
        reject(new Error(data))
      }
    })
  })
})
.then(function(dataB) {
  return new Promise(function(resolve, reject) {
    getDataC(params, function(data) {
      if(data){
        resolve(data)
      }else {
        reject(new Error(data))
      }
    })
  })
})
// ...
.catch(function(err) {
  // ...
})

结论

1.Promise虽然未解决地狱回调,但是解决了代码纵向发展的趋势,使可读性更高
2.虽然解决了纵向发展的问题,但是在上面的例子请求数据时,最后的请求无法直接使用第一次请求的结果

参考

ECMAScript 6 入门中Promise章节

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容