11. 解释 Callback Hell & 使用Promise

今天也是非同步资料请求的主题,复习一下在XHR(XMLHttpRequest)搭配Promise的用法!

Callback & Callback Hell


如果一个function被作为参数传进另一个function,这种行为称为"Callback",
作为参数的function 则称作"callback function"。

来看个例子:

doSomething(function(result){console.log(result);}, failureCallback)

假设doSomething()执行的是,回传成功执行参数1(匿名函式),回传失败则执行参数2(failureCallback)。

被传入doSomething()作为参数的两个function,就被称作callback function。

但当我们在函式里执行更多行为时,被嵌套的callback会越来越多层,然後程序码会变成这样的结构:

function failureCallback() {
  console.log("failed");
}

doSomething(function(result) {
  doSecondThing(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

这种 Callback被一层层嵌套,导致 程序码难以阅读和维护 的情况,就被称作 Callback Hell。*

或是例子不够生动,可以看看网路上的图片:
https://ithelp.ithome.com.tw/upload/images/20210912/20129476WBMI112r7I.jpg

而为了避免产生callback Hell,现在多以使用promise的方式取代。

Promise


定义

Promise 物件代表一个即将完成、或失败的非同步操作,以及它所产生的值。
引用自Promise - JavaScript | MDN

意思是, Promise 代表 在进行非同步请求时,成功取得的资料失败的物件

每当一个host要连结资料库(database management system),资料库应该回传一个Promise。

Promise 的建构式语法:

new Promise( /* executor */ function(resolve, reject) { ... } );

new是指产生一个新物件,所以这里会产生一个Promise的空物件,Promise 里面包含 resolve 或 reject 两种 callback function:

  • resolve 为成功取得的资料时,回传资料。
  • reject 则会在失败时回传 error object。

每当传入这两个参数,executor函式就会直接执行,并在成功时执行resolve,在失败时执行reject

而promise很常被搭配使用两个function:

  • .then() 接住 resolve 的结果。
  • .catch() 接住 reject 的结果。

举例:

// 设定回传resolve的情况
let example = new Promise((resolve, reject) => {
	resolve({type: "object"});
});

example.then((data)=>{console.log(data);});

在这个范例里,我们设定resolve会回传一个object。
并且在执行.then()後,把资料印出来。

// 设定回传reject的情况
let example = new Promise((resolve, reject) => {
	reject(new Error("not allowed"));
});

example
  .then((data)=>{console.log(data);})
  .catch((error) => {console.log(error);});

而这个范例,是设定在reject时,产生一个error object。
并在.catch()抓到错误後,印出错误讯息。

promise的function是可以被串接起来的,被称为 Promise Chain。

let example = new Promise((resolve, reject) => {
	resolve({ string : "Hello" });
});

example
  .then((data)=>{console.log(data);})
  .then(() => {console.log("HI");})
  .catch((error) => {console.log(error);});  
  
  // output: { string : "Hello" } ; "HI"

这样的好处是,不管接续执行多少工作(.then()),最後都只要指定一次失败的情况(.catch())。
而不必像早期的方式(Callback Hell的范例),传入许多次failureCallback

看完范例,最後来尝试实际应用Promise。

Promise with XHR


延续昨天显示气温的例子,今天试着用Promise改写内容。

附上程序码(和原来写法的差异,写在注解里):

  const section = document.querySelector('section');

  // 获得URL; 请记得将授权码字样替换掉!
  var requestURL = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-D0047-063?Authorization=授权码&limit=1&offset=7&format=JSON&elementName=T';

  function loadData(url){
    
     return new Promise(function(resolve, reject) {
       
        var request = new XMLHttpRequest();
        request.open('GET', requestURL);
        request.responseType = 'json'; 
        
        request.onload = function() {
          // 如果正确获得资料,将资料写进resolve
          if (request.status === 200) {
            resolve(request.response);
          } else {
            // 如果失败,回传错误讯息
            reject(Error('error code:' + request.statusText));
          }
        }
      
        request.send(); 
    });
  }

  // promise chain
  loadData(requestURL)
    .then((response)=>showWeather(response))
    .catch((error)=>console.log(error));

  function showWeather(jsonObj) {
      var jsonT = jsonObj['records']['locations'][0]['location'][0]['weatherElement'][0]['time'][0]['elementValue'][0]['value'];
      const temperature = document.createElement('h1');
      temperature.textContent = "地点: 台北市, 现在气温: " + jsonT + ' °C ';
      section.appendChild(temperature);
  } 

运行结果:
https://ithelp.ithome.com.tw/upload/images/20210912/20129476yDP4QOpTM1.jpg

结论

附上一些和Promise相关的问题:

【如内文有误还请不吝指教>< 谢谢阅览至此的各位:D】

-----正文结束-----


<<:  D11: 工程师太师了: 第6话

>>:  【LeetCode】Binary Search

Day 28 - WooCommerce: 显示虚拟帐号付款资讯

昨天虽然完成了以永丰银行虚拟帐号付款方式进行结帐,但如果没有找个地方显示帐号,顾客也不知道要汇钱到那...

Gulp 基础介绍 DAY78

在介绍 gulp 之前 当然需要知道 gulp 它是什麽 简单来说 gulp 就是 基於node.j...

DAY 27 Big Data 5Vs – Value(价值) – QuickSight(1)

最後一个「价值Value」也是资料分析最重要的阶段,很重要是因为,只有在资料分析後可以产生比原始资料...

Python 关系运算符号和if用法

今天要来教大家数学的关系运算,也就是大於、等於、不等於...等等的,还有if的用法,就是假如某件事成...

【Day 15】 为何要进行资安攻击的分析

大家早安~ 接下来就是到了我们实作二 - 资安攻击分析 on AWS,在那之前想先跟大家讨论 Q:『...