JavaScript之Promise

看完下面文章,你将了解到:

1、什么是Promise?
2、Promise怎样使用以及使用的场景,解决了什么问题
3、ES7较ES6又增加了哪些支撑Promise的语法糖
复制代码

一、Promise

Promise顾名思义,字面理解是"承诺",那承诺什么呢?下面我们就来探究下.
首先,我们先看看Promise是怎么使用的,然后再试着去理解它.
该新特性属于 ECMAScript 2015(ES6)规范.
Promise.prototype 属性表示 Promise构造器的原型.它允许你可以在构造器的原型对象添加属性或方法到所有 Promise 实例上.
复制代码

我们先来看下,Promise.prototype是什么东西?在浏览器打印看下,下图:

可以看到Promise原型上,默认定义了catch,finally,then方法和一个返回的构造函数constructor.

现在我们就来一一举个例子:

    Promise.prototype.constructor();//彩蛋放后
    Promise.prototype.catch(onRejected)
    Promise.prototype.then(onFulfilled, onRejected)
    Promise.prototype.finally(onFinally)
复制代码

1、Promise.prototype.catch(onRejected) 该方法返回一个Promise函数,回调函数添加一个拒绝(rejection),该回调函数处理拒绝的情况.等价与Promise.prototype.then(undefined, onRejected)

感觉还是一脸懵逼是吧,来看下例子: 便于理解,我这里参数就随便定义了success,fail

let promise = new Promise(function(success,fail){
    throw 'fail';
});
promise.catch(function(resouce){
    console.dir('then resouce==>'+resouce);
});
等价与Promise.prototype.then(undefined, onRejected)
复制代码

2、Promise.prototype.then(onFulfilled, onRejected) 该方法返回一个Promise函数,回调函数添加一个then,该回调函数最多可添加两个参数,返回成功信息和失败的状态信息.

let promise = new Promise(function(success,fail){
    success('have successed');
});
promise.then(function(data){
    console.dir('then data==>'+data);
},function(err_reso){
    console.dir('then reso==>'+err_reso);
})
复制代码

等价于

let promise = new Promise(function(success,fail){
    success('have successed');
});
promise.then((data)=>{
    console.dir('then data==>'+data);
}
).catch((resorce)=>{
     console.dir('then resorce==>'+resorce);
});
复制代码

到这里,你应该看明白了吧,为了更专业性,下面我的写法会有所变化了...

3、Promise.prototype.finally(onFinally) 该方法补充了then和catch方法---执行catch,then之后都会执行的事件处理回调.避免在then和catch重复的代码.

let promise = new Promise(function(resove,reject){
    resove('have successed');
});
promise.then((data)=>{
    console.dir('then data==>'+data);
}
).catch((e)=>{
     console.dir('then resorce==>'+e);
}).finnaly(()=>{
    // all loading
    console.dir('all loading');
});
复制代码

二、Promise之链式调用

看了上面的例子,好像Promise类似于Ajax的用法,又像一个代理. 没错Promise还真就做了这些事情.文章一开头说的,Promise为承诺而生.它承诺你在某个时候或某个状态执行给予反馈结果.

由于JavaScript的代码都是单线程的,导致所有网络操作,浏览器事件都必须是异步操作.通常的异步执行有三种,回调函数、事件监听以及发布订阅.我们这里只谈回调函数

1、先看个例子:

function foo(){
    console.log('1秒后执行');
}
let fn=setTimeout(function(){
    foo();
},1000);
//1秒后执行
复制代码

上面是个典型的回调函数的实现.

2、再来看下下面的例子:

function foo(){
    console.log('1秒后执行');
}
function fo(){
    console.log('1秒后执行');
}
setTimeout(function(){
   fo();
   setTimeout(function(){
    foo();
},1000);
},1000);
复制代码

如果有n个需求呢,是不是一直这么定义下去.看上去是不是很乱,不美观难以维护呢?

Promise首先最直观的的解决了,这个问题,让代码变得更为简洁,它采用链式的方式

3、对上面的例子做已调整,如下

function fo(){
    console.log('1秒后执行');
}
let promise =new Promise(function(resove,reject){
       setTimeout(function(){
       return resove(fo);
    },1000);
});
promise.then((data)=>{
     setTimeout(function(){
       return resove(data);
    },1000);
}).then((data)=>{
    console.log(data);
});
复制代码

以上代码是不是整洁了很多,有人会质疑promise不可能就这点能耐吧!

不着急,我们先来看下链式的原理,更进一步的了解下,然后再探讨其它的用法:

Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象

一个 Promise有以下几种状态:

pending: 初始状态,既不是成功,也不是失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
我们用视图来梳理下:
复制代码

pending 状态的 Promise 对象可能触发fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。

因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。

出自 Promise.then() – JavaScript | MDN 通过上面介绍,相信对Promise的优点有了一定的了解了, 下面就常用的场景,再补充一下:

4、图片加载为例:


"en">

  "UTF-8">
  "viewport" content="width=device-width, initial-scale=1.0">
  "X-UA-Compatible" content="ie=edge">
  Promise
  


  
"wrap">
"loading">正在加载...
"pics">
复制代码
function loadImg (url) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = function () {
      resolve(img)
    }
    img.onerror = reject
    img.src = url
  })
}
复制代码
//递归调用
function syncLoad (index) {
  if (index >= urls.length) return Promise.resolve()
  return loadImg(urls[index])
    .then(img => {
      // addToHtml(img)
      return syncLoad (index + 1)
    })
}
 
// 调用
syncLoad(0)
  .then(() => {
  document.querySelector('.loading').style.display = 'none'
}) 
.catch(console.log)
复制代码

介绍完了链式处理Promise对象的实例之后,我们再来研究下.并发请求...

三、并发请求(Promise.all)

对于不需要按顺序加载,只需要按顺序来处理的并发请求,Promise.all 是最好的解决办法。因为Promise.all 是原生函数,我们就引用文档来解释一下。

Promise.all(iterable) 方法指当所有在可迭代参数中的 promises 已完成,或者第一个传递的 promise(指 reject)失败时,返回 promise。 出自 Promise.all() – JavaScript | MDN

const promises = urls.map(loadImg)
Promise.all(promises)
  .then(imgs => {
    imgs.forEach(addToHtml)
    document.querySelector('.loading').style.display = 'none'
  })
  .catch(err => {
    console.error(err, 'Promise.all 当其中一个出现错误,就会reject。')
  })
复制代码

并发请求,按顺序处理结果

Promise.all 虽然能并发多个请求,但是一旦其中某一个 promise 出错,整个 promise 会被 reject.所以我们只要知道哪些图片出错了,把出错的图片再做一次请求或着用占位图补上就好.然后按顺序处理结果

方法一:

let task = Promise.resolve()
for (let i = 0; i < promises.length; i++) {
  task = task.then(() => promises[i]).then(addToHtml)
}
复制代码

方法二:

promises.reduce((task, imgPromise) => {
  return task.then(() => imgPromise).then(addToHtml)
}, Promise.resolve())
复制代码

四、并发请求(Promise.race)

Promise.race 接受一个 Promise 数组,返回这个数组中最先被 resolve 的 Promise 的返回值。
Promise.all接受一个 Promise 数组,返回这个数组中最后被 resolve 的 Promise 的返回值。
复制代码

通俗一点的理解:

all方法的效果实际上是【谁跑的慢,以谁为准执行回调】
race方法的效果实际上是【谁跑的快,以谁为准执行回调】
复制代码

因与all用法相似,这里就不做过多赘述了,下面我们来看下,ES7较ES6又增加了哪些支撑Promise的语法糖...

五、ES7支撑Promise的语法糖

老规矩,下看下实例:

function successCallback(result) {
  console.log("It succeeded with " + result);
}

function failureCallback(error) {
  console.log("It failed with " + error);
}
doSomething(successCallback, failureCallback);
复制代码
async function foo() {
  try {
    let result = await doSomething();
    let newResult = await doSomethingElse(result);
    let finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch(error) {
    failureCallback(error);
  }
}
复制代码

上面的例子,是否发现async、await这样的字眼,不见了then,catch了,对这就是在ECMAScript 2017标准的async/await语法糖中,这种同步形式代码的整齐性得到了极致的体现. 以上代码等价与

function foo() {
doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
}
复制代码

好处不言而喻

最后总结一下:

Promise是ES6的新特性,
简化异步请求层层回调的操作,
all和race实现并发请求,为很多业务场景提供了最佳的解决方案.
以及ES7新增的语法糖async,await大大简化了,Promise复杂的处理逻辑.
以上为个人一点点见解,如有错误或疑问,欢迎留言批评指正,如果没有设计到的请大家提供一下新的方式和方法。
复制代码

参考资料 JavaScript Promise:简介 | Web | Google Developers JavaScript Promise迷你书(中文版) JavaScript 之MDN web docs

你可能感兴趣的:(JavaScript之Promise)