Web前端之路让前端飞

Promise in Javascript

2017-06-15  本文已影响56人  细密画红

promise you have a good time here !

The Promise object is used for deferred and asynchronous computations.
简单来说,promise 是一个用来处理延迟和异步的对象。

故事要从回调开始说起

callback(回调) 是 javascript 默认的用来处理异步请求的方法。

通常我们将一个函数 A 当做另一个函数 B 的参数传入,在未来满足某个条件的情况下,函数 B 会调用 A 。例如:

function loadImg(src,parent,callback){
      var img=document.createElement('img');
      img.src=src;
      img.onload=callback; // 图片加载完成后调用回调函数
      parent.appendChild(img);
} 

回调的代码是可以正常工作的。那么它会有什么问题呢?

如果这里的callback也需要一个回调参数呢?然后这个回调参数又需要另一个回调?你知道这个问题是可以无限进行下去的吧。。。于是就有了这样的代码,传说中的 Pyramid of Doom ! 金字塔式的代码调用。

image.png

这里问题就很明显了

那么如果我们的代码可以写成这样呢:


image.png

Promise

4个阶段:stages of promise
  • Pending : waiting.一般初始化一个promise对象时的状态就是pending,这个时候还没有成功或者失败。
包裹 :wrapping

**promise is a try-catch wrapper around code that will finished at an unpredictable time. **

异步代码的特点就是我们不知道它什么时候结束。promise 就是对这类代码的一层包装而已,nothing more. 这样包装的好处就是不用再像之前的回调地狱那样来写代码。

那么 promise 的代码怎么写呢:coding

promise是一个构造函数

image.png

小练习1:包装一个图片请求

function loadImg(src,parent,callback){
      var img=document.createElement('img');
      img.src=src;
      img.onload=callback; // 图片加载完成后调用回调函数
      parent.appendChild(img);
} 

对这段代码用promise包装一下,如下:

new Promise(function(resolve,reject){
   var img=document.createElement('img');
   img.src='image.jpg';
   img.onload=resolve,
   img.onerror=reject;
   document.body.appenndChild(img);
})
 .then(finishLoading)
 .catch(showAlternateImage);

一旦 resolve 或者 reject 被调用了,就说明这个 promise 已经被处理的(settled)了,然后方法链上的下一个then(或者catch)开始被调用。

小练习2:包装一个setTimeout

<script>
  function wait(ms){
   /*
    要求:
    1. 用 promise 来保证 setTimeout 方法,在 setTimeout的回调里调用resolve()
    2.注意要让wait方法返回这个promise
   */
    window.setTimeout(function(){},ms);
 }
</script>

答案:

function wait(ms){
  return new Promise(function(resolve){
     setTimeout(function(){
       resolve('hello world');
    },ms);
  });
}
wait(300).then(function(data){
  console.log("this is "+data);
});

小练习3:包装一个xhr

function get(url){
  var req=new XMLHttpRequest();
   req.open('GET',url);
   req.onload=function(){
     if(req.status==200){
       // 请求成功,可以把返回数据res.response传给resolve方法中
     }else{
       // 请求失败,可以把失败信息 req.statusText传给reject中
     };
   };
  req.onerror=function(){
     //请求失败,把"Network Error" 传给reject
  };
  req.send();
}

答案:

function get(url){
 return new Promise(function(resolve,reject){
    var req=new XMLHttpRequest();
      req.open('GET',url);
      req.onload=function(){
        if(req.status==200){
          resolve(req.response);
       }else{
         reject(req.statusText);
       }
     };
     req.onerror=function(){
       reject(Error("Network Error"));
   };
   req.send();
 });
}
//调用
get("http://www.test.com/")
.then(function(res){
     console.log(res);
})
.catch(function(res){
    //handle the error
});

Fetch API

前面的 xhr对象 和 promise 的包装代码是不是很烦人?获取数据不应该这么麻烦。chromn里自带的 fetch API 可以让我们省去很多 xhr 对象的麻烦设置,而且,it is built on native promise !

Fetch API 用法:

fetch('https://www.test.com/',{
  method:'get'
}).then(function(response){

}).catch(function(error){
 
});

记得上一章写的那个很长的 get(url) 吗?额。。。其实它只要这么写就可以了...

function get(url){
   return fetch(url,{
    method:'get'
  }
}

thenable

then方法返回的是一个 promise 对象,我们可以在一个初始方法(一般返回promise对象)上调用后连接 then方法,也可以在 then 方法后面连接 then 方法,因为它自身返回的也是 promise对象。开发者一般用 thenable 这个词来描述 promise 对象和 .thens.

any method or object that returns .then is thenable.
anything thenable can become a part of chain of asynchronous work.

当创建一个异步调用链时,链上的每个方法接收到的,要么是前面的 promise 成功时传递过来的参数,或者是前一个 then 方法返回的值。这样你就可以在异步的方法之间传递参数。

be able to chain thenables 对于简化异步代码的顺序调用非常有用。

Error Handling Strategies 错误处理策略

image.png

注意,这两种错误处理是一样的。.catch() 方法 其实是 .then ( undefined , rejectFunc ) 的缩写而已。例如,
get('example.json').then(resolveFunc,rejectFunc) ,如果get方法里有错误的话,rejectFunc就会被调用。

总的来说,一旦promise 是rejected的,javascript引擎就会跳转到调用链上的下一个rejectJFunc,无论这个方法是写在catch里还是then里。

但还是建议使用catch方法,因为可读性强一些,并且写起来方便一些。

注意,.catch 和第二个回调的rejectFunc在执行的顺序会有不同。如下所示:


image.png

上面的代码块,如果resolveFunc出错了,那么下面catch里的这个rejectFunc就会被执行。这两个方法是可以同时被执行的。但如果用下面代码行的写法,同一个 then 里的两个方法,只有一个会被执行,或者两个都不执行,如果这里resolveFunc出错了,那么javascript引擎会尝试寻找 then 之后的链上的rejectFunc方法。

chaining stage : chaining promises together

在处理多个异步请求时有两种实现方式

同步代码总是按顺序执行的,但是异步代码可以顺序,也可以并行执行。

看看下面这段代码有什么问题?

image.png

answer: 我们无法预测请求返回的顺序,所以我们不知道哪一个getJson promise会先resolve, 就是说他们resovle 的顺序和他们被创建的顺序可能会不一样。这样的话,thumbnails方法的创建就会是随机的。当然,这并不是一个错误。但它引出了一个问题,我们怎么样才能按我们所期望的顺序来创建这些thumbnails方法?

创建一个按顺序执行的promise串(利用数组方法,不再手工创建promise 串)
思路:

image.png

解决方法一:

image.png

但是这样做有问题吗?
good news: thumbnails按顺序执行
bad news: 同步执行,时间叠加

image.png

解决方法二:

image.png

执行时间:

image.png

问题:不能保证请求顺序

解决方法三:不用sequence,使用map

image.png
Promise.all
image.png
上一篇下一篇

猜你喜欢

热点阅读