流程控制: jQ Deffered 与 ES6 Promise 使用入坑!

Arl73H 3年前
   <p> </p>    <p><img src="https://simg.open-open.com/show/86f3c750938dc90dbfe0f7e816a96efd.jpg"></p>    <h2>从 jQuery $.Deferred() 开始</h2>    <p>说到异步流程控制,之前用的比较多的是jQ的Deferred。那Deferred是个啥呢,控制台来打印看下:</p>    <p><img src="https://simg.open-open.com/show/68a14eeb54652de0fcd97e9a5e55aa0d.png"></p>    <p>喔!看得出$.Deferred()后是个对象,其 <em>下面</em> 有着熟悉的 done , fail , always 字眼(对,没错,ajax里经常用到的就是这些货色), 当然了,还有最最重要的 reject 和 resolve 方法,说到这两个方法,就得引出下Deferred的状态机制了,其实很简单,实例化后用上图中的 state 方法就可以查看( $.Deferred().state() ),有三种状态</p>    <ul>     <li> <p>执行resolve/reject前,返回值是pending</p> </li>     <li> <p>执行了resolve,返回值是resolved</p> </li>     <li> <p>执行了reject,返回值是rejected</p> </li>    </ul>    <p>来试着用下!</p>    <pre>  <code class="language-javascript">function log (msg) {      console.log(msg);  }  // 申明个异步操作  var Async = function () {      // 生成一个0到5秒的延迟      var delay = Math.floor(Math.random() * 5);      // 创建一个Deffered对象      var dfd = $.Deferred();      // 这里调用一个异步操作      setTimeout(function(){          if (delay <= 2) {              // 置dfd状态为resolved              dfd.resolve('一切正常!');          } else {              // 置dfd状态为rejected              dfd.reject('超时了!');          }                  }, delay * 1000)      // 这里要返回Deferred下的promise对象Dererred对象的原因下面会解释      return dfd.promise();  }    Async()      .done(function (data) {          log(data) // 如果延迟不大于三秒 输出dfd.resolve()中的数据 '一切正常!'      })      .fail(function (err) {          log(err) // 反之则 输出dfd.reject()中的数据 '超时了!'       })      .always(function () {          log('执行完毕!'); // 总是输出 '执行完毕!'      })</code></pre>    <p>尝试下通俗理解整个流程就是</p>    <ol>     <li> <p>在某个操作 <em>开始前</em> 创建一个 Deferred 对象,然后执行操作</p> </li>     <li> <p>操作间可根据情况给dfd执行 relove 或者 reject 方法改变状态并传入数据</p> </li>     <li> <p>最后返回出dfd的对象下的一个promise对象,这里不直接返回dfd对象是因为dfd对象的状态是在第一次resolve或者reject后还可以更改的(不过里面的数据以第一次为准)!!</p> </li>     <li> <p>操作执行后用 done 和 fail 方法分别接受resolve和reject状态和数据(一一对应)然后执行回调(其实1.8还有个 then 方法,接受两个参数,第一个参数为 resolve 的回调,第二个为 reject 的)</p> </li>     <li> <p>always 是无论 resolve 还是 reject 都会执行。</p> </li>    </ol>    <p>讲个比较烂的比喻啊,我是一个流水线车间质检工人,就在平常的这样的一天,来了一批玩具熊,嗯,接下来应该是这样的</p>    <ol>     <li> <p>来了一个检查目标( $.Dererred() ),这时你还不知道它是好是坏</p> </li>     <li> <p>我靠我几十年的新东方炒菜技巧检验产品并给良品贴上了合格标签( dfd.res* olve(合格标签) ),次品贴上回厂标签* ( dfd.reject(回厂标签及原因) )</p> </li>     <li> <p>然后通过的良品和次品都来到了各自的包装口打好包,不能对里面的标签做更改了!( dfd.promise() )去往自己下一个目的地( return dfd.promise )</p> </li>     <li> <p>再然后良品来到了熊孩子手中( .done() ),次品回到了厂里( .fail() ),最后不管玩具熊到了哪里,其实都会被开膛破肚( .always() 好吧这里有点牵强)</p> </li>    </ol>    <p>这里再上一张图来解释下!</p>    <p><img src="https://simg.open-open.com/show/7c3a53ad573debaf24e0f9d6d012454b.png"></p>    <p>还有值得说一下的是 always 里的回调,我在实际中使用时发现总是在 done 和 fail 里的回调(假设为同步)执行完毕后后执行的。</p>    <h2>接下来是ES6 Promise!</h2>    <p>不说什么,先打印一下!</p>    <p><img src="https://simg.open-open.com/show/a23c42c1156d60950d795e5291b7ab83.png"></p>    <p>可以看到Promise下也有熟悉的 resolve 和 reject 方法,好像和jQ的 Deferred 颇为相似!但是不是少了点什么呢? done 或者 fail 之类的流程控制的方法呢??</p>    <p>不急,其实展开 prototype 原型上就可以看到挂载着的 then 方法了!(像极了jQ1.8后那个 then ,不过我觉得应该说是jQ来遵循 Promise 才对)</p>    <p><img src="https://simg.open-open.com/show/bf589cf2a00ee8338c1a5bc5c37fab0c.png"></p>    <p>Promise其实就是个构造函数,还是之前的例子,这里我们分三步走</p>    <pre>  <code class="language-javascript">var Async = function () {      // 第一步,新建个promise对象,所需的异步操作在其中进行      var prms = new Promise(function(resolve, reject){          // 生成一个0到5秒的延迟          var delay = Math.floor(Math.random() * 5);          // 这里调用一个异步操作          setTimeout(function(){              // 第二步, 根据情况置promise为resolve或者reject              if (delay <= 2) {                  // 置dfd状态为resolved                  resolve('一切正常!');              } else {                  // 置dfd状态为rejected                  reject('超时了!');              }                      }, delay * 1000)      })      // 第三步,返回这个Promise对象      return prms  }    // 强大的来了  Async()      // then接受两个函数分别处理resolve和reject两种状态      .then(      function(data) {          console.log(data) // 一切正常!      },       function(err) {          console.log(err) // 超时了!!      })</code></pre>    <p>粗粗一看好像和 Dererred 不能更像了,,不过细心点的话可以发现我们在函数里直接返回了 prms 这个对象,而不是像之前把包装了一层。。。对!因为 Promise 的特性就是一旦第一次赋予了状态后面就无法更改了,这也算省心多了吧。但是问题来了,我为什么要选择用 Promise 呢??</p>    <p>这么说吧, <strong>它是原生的 它是原生的 它是原生的!</strong> ,还有 <strong>可以链式链式链式链式调用!</strong> ,我们可以把每一个 then 或者 catch 当做一个处理器, 比如这样</p>    <pre>  <code class="language-javascript">Async()      // 这里暂时只处理resolve      .then(function(data) {          console.log(data) // 一切正常!          return Promise.resolve('随便什么');      })      // 下一个then处理器接收到上一个处理器发出的数据      .then(function(data2) {          console.log(data2) // 随便什么          return Promise.reject('错误数据');      })      ...</code></pre>    <p>对!没看错,其实在 then 里面你还可以 return 其他的 promise 对象传并递数据!更有甚你甚至可以什么都不返回,比如说这样</p>    <pre>  <code class="language-javascript">Async()      .then(function(data) {          console.log(data) // 一切正常!      })      // 上面那个处理器如果不return任何东西 就会默认返回个resolve(undefined)      // 然后下面的处理器就会接收到这个resolve(undefined)      .then(function(data2) {          console.log(data2) // undefined          // 虽然没有数据来处理,但是你还可以在这里做一些事情啊,例如          return Promise.reject('错误数据');      })      // 嗒哒,catch就这么登场了,这里用catch处理上个then处理器发出的reject      .catch(fucntion(err){          console.log(err) // 错误数据          return '那直接返回个字符串呢?'      })      // 上个catch处理器返回了个字符串其实也会被下个处理器接受      // 相当于resolve('那直接返回个字符串呢?')      .then(function(data3){          console.log(data3) // 那直接返回个字符串呢?      })      // 好,接着我们来试试在没有返回任何东西的情况下接一个catch处理器      .catch(function(err2){          console.log(err2)           // 我们可以来猜一下上面会输出什么,undefined吗?          // 错,其实这里什么都不会输出,因为这个catch接收的是resolve          // 但它并不会吞没这个resolve而是选择跳过,例如我们这里再返回          return Promise.resolve('这个字符串会被跳过')      })      // 这里紧接着个then处理器,它接受到的数据呢      // 其实并不是上个catch返回的resolve('这个字符串会被跳过')      // 而是catch之前那个then处理器默认返回的resolve(undefined)      .then(function(data4){          console.log(data4) // undefined      })</code></pre>    <p>有点被绕晕了吧 <img src="https://simg.open-open.com/show/718f6b87f2db08da3ee5bb8aa76f83b9.gif"></p>    <h3>我们用一句话来梳理下:</h3>    <p>链式调下会有一串 then 和 catch ,这些 then 和 catch 处理器会 <em>按照顺序</em> 接受 <em>上个处理器</em> 所产生的返回值,并且根据 <em>传入的状态</em> 做出 <em>不同</em> 响应,要么跳过,要么处理(所以上面23行处的 catch 处理器被跳过了)</p>    <p>ps: 上面我们用的 then 处理器只有一个函数参数,所以只会处理 resolve 状态,如果是两个 then 就可以处理 reject 了。</p>    <p>写的还是很粗糙,有错误的地方希望多多指教!</p>    <p>来自: <a href="/misc/goto?guid=4959672514466305049" rel="nofollow">https://segmentfault.com/a/1190000005072394</a></p>