JavaScript学习日记 : Day16 - Promise

因为JavaScript属於同步的语言,一次只能作一件事情,遇到非同步的事件就会把该事件挪到最後执行。

console.log("Start!");

setTimeout(() => { console.log("非同步事件") },1000)

console.log("End!")

// 

Start!
End!
非同步事件

Ajax也一样属非同步行为,如果要确保撷取到远端资料後,并且针对远端资料作处理的话,那了解promise的运作就非常重要。以下例子为Promise base的Ajax函式库axios为例子:

let data = [];

axios.get('https://randomuser.me/api/').then((res) => {
    data = res
    console.log(data) // random user data
})

console.log(res) // []

Promise object的构造器语法如下:

let promise = new Promise(function(resolve, reject) {
  // executor
});

传递给new Promise的函数被称为executor。当new Promise被创建,executor会自动运行。
当executor获得了结果,会执行以下两个callback:

  • resolve(value) ---如果任务完成并带有结果value
  • reject(error) ---如果出现了error,error即为error object

new Promise构造器返回的promise对象具有以下的内部属性:

  • state --- 最初是pending,然後在resolve被调用时变为fulfilled,或者在reject被调用时变为reject
  • result ---最初是undefined,然後在resolve(value)被调用时变为value,或者reject(error)被调用时变为error。

举一个promise构造器和一个简单的executor函数:

let promise = new Promise(function(resolve, reject) {
  // 当new promise时,自动执行此函数

  // 1秒後执行成功,带有value "done"
  setTimeout(() => resolve("done"), 1000);
});

经过一秒的处理後,executor调用resolve('done')来产生结果,改变promise object的状态:

reject的例子:

let promise = new Promise(function(resolve, reject) {
  // 1 秒后发出工作已经被完成的信号,并带有 error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

executor只能调用一个resolve或是一个reject。所以再对resolve或是reject的调用都会被忽略。

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // 被忽略
  setTimeout(() => resolve("…")); // 被忽略
});

尽量以Error object调用reject

Resolve/reject可以立即进行(无非同步)
实际上executor通常是进行某些非同步操作,并在一段时间後调用resolve/reject,但这不是必须的

let promise = new Promise(function(resolve, reject) {
  // 不花时间去做这项工作
  resolve(123); // 立即给出结果:123
});

state与result都是内部属性,无法直接获取,但我们可以使用.then/.catch/.finally等方法。

then

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

.then接收两个函数,第一个函数接收promise resolve後的结果,第二个则接收promise reject的结果。

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(
  result => alert(result), // 1秒後显示 "done!"
  error => alert(error) // 不运行
);

reject的情况下:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

promise.then(
  result => alert(result), // 不运行
  error => alert(error) // 1秒後显示 "Error: Whoops!"
);

如果只对成功完成感兴趣,那也可以只提供一个函数。

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // 1秒後显示 "done!"

catch

如果只对error感兴趣,可以使用null作为第一个参数,.then(null,errorHandleFunction),或者使用.catch:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f)跟promise.then(null, f)是一样的
promise.catch(alert); // 1秒後显示 "Error: Whoops!"

finally

finally中的callback function是在promise执行resolve或reject後执行,是一个很好执行清理(clean up)或是处理程序(handler),例如无论执行的结果如何都可以在finally中把loading indicator关掉。

new Promise((resolve, reject) => {
  // 做一些处理後调用resolve or reject
})
  // promise为settled,不管成功与否
  .finally(() => stop loading indicator)
  // 所以在finally中的处理,都会在我们处理成功/错误结果之前
  .then(result => show result, err => show error)
  1. finally的handler并没有参数,所以在finally中我们并不知道promise是否成功
  2. finally处理结果会跟promise的处理结果一起传给then
new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Promise ready"))
  .then(result => alert(result));

或是一起传给catch

new Promise((resolve, reject) => {
  throw new Error("error");
})
  .finally(() => alert("Promise ready"))
  .catch(err => alert(err)); 

Promise chain

可以在.then()中回传一个promise,那下一个.then()就会等到回传的promise调用resolve或reject:

new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000)
}).then((result) => {
    console.log(result);
    return new Promise((resolve, reject)=> {
        setTimeout(() => resolve(2), 2000)
    })
}).then((result) => {
    console.log(result);
})

Error handle

通常会在Promise chain中的末端利用.catch()进行错误处理,所以可能会在多个.then()後才出现,如果整个promise chain都没有报错,那.catch()就不会被执行。

  • 隐式 try ... catch

直接看一个简单的例子:

function testError() {
    return new Promise((resolve, reject) => {
        nonExistMethod();
    })
}

testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })

// error catch successfully! ReferenceError: nonExistMethod is not defined

为什麽没有执行reject,error也会被.catch捕抓到呢? 原因是因为Promise内部有一层try..catch所包住,而且catch的部分预设使用reject function。

那如果error是出现在Promise外面呢?

function testError() {
    nonExistMethod();
    
    return new Promise((resolve, reject) => {
        resolve("result!")    
    })
}

testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })

这样的情况下,promise chain的.catch是没有办法捕捉到error的,必须自己利用try..catch来补捉:

function testError() {
    nonExistMethod();
    
    return new Promise((resolve, reject) => {
        resolve("result!")    
    })
}

try {
   testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) }) 
} catch(error) {
    console.log("handle by outer try-catch")
}

Promise methods

最後介绍Promise的其他方法:

  • Promise.all --- 多个promise同时执行,全部完成後统一回传
  • Promise.race --- 多个Promise同时执行,只回传第一个完成的
  • Promise.resolve, Promise.reject --- 用来定义Fulfilled或Rejected的Promise物件

说明这些方法前,先定义一个promise函数,可以传入两个参数:

function ownPromise(count, time = 1000) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            count? resolve(`第${count}次成功!`) : reject("失败")
        },time)
    })
}
  1. Promise.all

以数组的形式传入多个promise函数,结果一样以数组的形式回传。

Promise.all([ownPromise(1), ownPromise(2), ownPromise(3,4000)]).then((res) => { console.log(res) })

// ["第1次成功!", "第2次成功!", "第3次成功!"]
  1. Promise.race

以数组的形式传入多个promise函数,与Promise.all不同的是只会回传单一结果,也就是第一个完成的。

Promise.race([ownPromise(1), ownPromise(2), ownPromise(3,4000)]).then((res) => { console.log(res) })

// 第1次成功!
  1. Promise.reject, Promise.resolve

直接看例子会比较快明白:

let result = Promise.resolve("result");

result.then(res => { console.log(res) }) // result

let rejectResult = Promise.reject("error");

rejectResult.catch(rej => { console.log(rej) }) // error

<<:  Day14:14 - 购物车服务(2) - 前端 - 购物车总商品显示、加入购物车

>>:  Day13-Kubernetes 那些事 - Deployment 与 ReplicaSet(一)

[Day28] Flutter with GetX Socket.io

Socket.io 注意server side需要使用3.0.3版本 否则flutter clien...

Day 10 ( 中级 ) 雪花随风飘

雪花随风飘 教学原文参考:雪花随风飘 这篇文章会介绍,如何在 Scratch 3 里使用建立分身、绘...

【Day14】数据展示元件 - Card

元件介绍 Card 是一个可以显示单个主题内容及操作的元件,通常这个主题内容包含图片、标题、描述或是...

[Day16] 再战SAT

今天花了一整天Debug,一直看为甚麽「Not Work」,单纯纪录一下流程。 今日目标 修好昨天的...

Day 10 : 操作基础篇 7 - 使用 Workspaces 功能,快速取用不同的版面配置

前言 这是 Obsidian 使用教学 — 基础篇的第 7 篇文章。 在 上一篇文章 中,我分享了个...