promise 经常与 Ajax 共同谈论,但这篇文章会以 promise 为主;promise 是一个语法,专门用来处理与优化非同步行为,我们知道 JavaScript 属於同步程序语言,因此它一次只会做一件事,遇到非同步事件的时候,就会等到所有的原始码运行完,才会执行非同步的事件。
这边强调的是执行的顺序,只要是非同步一定都会放在最後才执行,即使 setTimeout()
的时间设定是 0,也同样放到最後。
function getData() {
setTimeout(() => {
console.log('... 已取得远端资料');
}, 0);
}
const component = {
init() {
console.log(1);
getData(); // 非同步
console.log(2);
}
}
component.init();
结果:
1
2
... 已取得远端资料
promise 是一个建构式函式,函式也属於物件,我们可以附加其他属性方法,promise 可以直接使用 all
、race
、resolve
、reject
等方法,下面这几种方法皆可以直接 console 查看:
promise 建构函式 new
出的新物件,也可以在 prototype
内使用其中的原型方法,在这里面也包含了 then
、catch
、finally
,如果要呼叫他们,则必须在新产生的物件下才能呼叫。
透过 new Promise()
的方法建立 a
物件,a
就能使用 promise 的原型方法,范例如下:
const a = new Promise();
a.then(); // promise 回传正确
a.catch(); // promise 回传失败
a.finally(); // 非同步执行完毕(无论是否正确完成,非同步都会在最後被执行完毕)
在 promise 事件里,我们必须给予参数让它回传结果,通常会给予 resolve
、reject
这两个参数,他们分别代表回传成功及失败,并且仅能回传其中一个。另外,这两个参数也可以自己定义名称,但大部分的开发者会习惯用本来的名字。
new Promise(function(resolve, reject){
resolve(); // 正确完成的回传
reject(); // 失败的回传
});
promise 的执行过程中也包括下面这几种状态:
resolve
的结果,resolve
会回传成功。reject
的结果,reject
会回传错误。以下范例可以大致说明 promise 的运行过程:
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
// setTimeout 在这里就是一个非同步的状态
setTimeout(() => {
if (status) {
resolve('promiseSetTimeout 成功')
} else {
reject('promiseSetTimeout 失败')
}
}, 0);
})
}
pending 之後会有两个状态 Fulfilled 和 Rejected,Fulfilled ( 成功 ) 会使用 resolve
回传结果,并用 then
来做接收,Rejected ( 失败 ) 会使用 reject
回传结果,并用 then
或 catch
来做接收,但如果前面有 then
,则会跳过,直接用 catch
做接收。
另外,我们要如何确定 promise 是否完成?这时候可以依据 resolve
及 reject
是否有被调用,如果没有被调用, promise 的结果会停留在 pending
。
function promise() {
return new Promise((resolve, reject) => {});
}
console.dir(promise()); // [[PromiseState]]: "pending"
执行上面程序码,我们可以看到在 promise 函式中有下面两种属性:
"pending"
目前的进度状态。undefined
为 resolve
或 reject
回传的值。promise
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
函式陈述式建立以後,透过 return new Promise
回传并建立一个 promise 物件,在内部执行 promise 函式并加入参数 resolve
和 reject
,这个阶段就是常见的 promise 结构,接着等待 resolve
、reject
回传结果就可以完成整个程序。
这个范例会随机调用 resolve 和 reject:
function promise() {
return new Promise((resolve, reject) => {
// 随机取得 0 or 1
const num = Math.random() > 0.5 ? 1 : 0;
// 1 则执行 resolve,否则执行 reject
if (num) {
resolve('成功');
}
reject('失败')
});
}
第二个范例也可以看到函式最後的结果是回传 resolve,并且能够回传 resolved 的状态和值。
const promiseSetTimeout = (status) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (status) {
resolve('promiseSetTimeout 成功')
} else {
reject('promiseSetTimeout 失败')
}
}, 0);
})
}
promiseSetTimeout(true)
.then(function(res){
console.log(res); // "promiseSetTimeout 成功" 回传成功
})
结果:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
promise 在 then
、catch
都可以使用链接的方式不断的进行任务,这个范例的 promise 传入 0
会调用 reject
,其他数值则是 resolve
。
function promise(num) {
return new Promise((resolve, reject) => {
num ? resolve(`${num}, 成功`) : reject('失败');
});
}
如果我们希望 promise 可以接着进行下一个任务,可使用 return
进入下一个 then
,这边的 return
也有一些特点:
return
会遵循 then
及 catch
的运作。then
则可以取得结果。promise(1)
.then(success => {
console.log(success);
return promise(2);
})
.then(success => {
console.log(success);
return promise(0); // 这个阶段会进入 catch
})
.then(success => { // 由於上一个阶段结果是 reject,所以此段不执行
console.log(success);
return promise(3);
})
.catch(fail => {
console.log(fail);
})
上面有提到几个 promise 的方法,这边回头来讨论一下,在 promise物件下,展开可以看到这些方法。
以阵列的形式传入数个 promise 函式,并且以同样的顺序回传,且为阵列结果。
Promise.all([promise(1), promise(2), promise(3, 3000)])
.then(res => {
console.log(res);
});
以阵列的形式传入数个 promise 函式,但在全部执行完毕以後只会回传单一个结果,并且回传的是第一个执行完毕的阵列,以下列范例来说,回传的是 promise(1)
。
Promise.race([promise(1), promise(2), promise(3, 3000)]).then(res => {
console.log(res);
});
这两个方法其实就是回传 promise 运行完毕的状态;resolve
回传操作成功,reject
回传操作失败,并且通常只会回传其中一个结果。
var result = Promise.resolve('result');
result.then(res => {
console.log('resolved', res); // 成功部分可以正确接收结果
}, res => {
console.log('rejected', res); // 失败部分不会取得结果
});
此篇文章感谢卡斯伯老师详细的文章讲解,为了学习和记忆我也尽量用自己的话整理出来,如果有理解错误的地方也请看过的大神们不吝啬告知,或也可以看看卡斯伯老师详细的 promise 全介绍。
参考资料:
<<: [NestJS 带你飞!] DAY31 - 实战演练 (下)
>>: Golang 学习笔记-- 快速上手/重点整理 - 3 - Array, Slice
启动Redis Server # 复制安装档内的redis.conf 到自己指定的路径下 cp /h...
终於来到了第九天,今天要再更深入介绍函式(Function)以及 Function Receiver...
今天要来讲的东西,主要是来自笔者以前看过很喜欢的文章(https://www.ithome.com....
什麽是市场区隔? 最早看到市场区隔这个词的时候 一直不知道算是名词还是动词 後来多看几次文章後慢慢了...
前情提要 上一集让人等到很崩溃的,终於..郑列终於吹嘘完了 阿物件:我跟你说... 我:... (接...