JavaScript 中的 Promise:2019 年完整指南
什么是承诺?
为什么我们在 JavaScript 中使用 Promise?
JavaScript 中的 Promise 究竟是如何工作的?
JavaScript 中何时引入了 Promise?
承诺中的不同状态
深入了解 Promise 对象
在 Javascript 中创建 Promise
在 Javascript 中调用 Promise
JavaScript 中的 Promise 链
JavaScript Promise 是同步的还是异步的?
以更简洁的方式实现 JavaScript Promise
使用 ES6/ES2015、ES7 编写 JavaScript Promise
结论
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
点击此处阅读原文
JavaScript 中的 Promise是一个重要的概念,对于 JavaScript 开发人员来说必须理解。如果理解了这个概念,开发人员就可以在日常工作中以各种方式运用它。
网上有很多关于 Promise 的文章和教程,但很少有能全面指导如何使用 Promise 的。本文将深入讲解 Promise,让你无需再参考其他资料。
什么是承诺?
根据 MDN 文档:promise 是一个对象,它表示异步操作的最终完成或失败及其结果值。
为什么我们在 JavaScript 中使用 Promise?
一般来说,JavaScript 是一种同步脚本语言。为了执行异步操作,Promise 非常有用。在 Promise 出现之前,处理多个异步任务时,通常会使用回调函数。
但过多的回调函数会导致代码难以维护,从而产生所谓的“回调地狱”。为了解决这个问题,人们开始使用 Promise。
没错,这里面有很多技术术语!但是,我认为如果用非技术性的方式来讨论,你会更容易理解承诺的含义。
JavaScript 中的 Promise 究竟是如何工作的?
你可以把 JavaScript 中的 Promise 理解为你在现实生活中做出的承诺。
想象一下,你答应女朋友要给她买一份昂贵的礼物。你不知道自己能否兑现承诺。也许你能做到,也许做不到。
所以,如果你答应了礼物但还没来得及买,这个承诺就处于待定状态。如果你能履行承诺,那么承诺就已实现。但如果你因为某种原因无法做到,那么承诺就处于拒绝状态。
JavaScript 中何时引入了 Promise?
Promise 并非全新概念。事实上,早在 1976 年,这个术语就已出现。2011 年初,jQuery 的延迟对象(deferred objects)让 Promise 的概念流行起来。延迟对象与 Promise 的概念类似,但它们并不完全遵循 ECMA 2015 版 Promise 规范中规定的技术规范。
最后,Promise 正式被添加到 ECMA 脚本 2015 规范中,并且也已在所有最新的浏览器和 Node Js 中实现。
承诺中的不同状态
这些概念同样适用于 Promise。Promise 可以处于以下任何一种状态。这些状态如下:
-
待定:与承诺相关的任务尚未完成或被拒绝。
-
已完成:与承诺相关的任务已成功完成。
-
已拒绝:与承诺相关的任务失败。
这里需要注意的一点是,创建 Promise 的函数能够跟踪 Promise 的状态。
深入了解 Promise 对象
var isPossibleToPurchaseGift = true;
var giftPromise = new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
resolve('You are able to keep your promise');
} else {
reject('You are unable to keep your promise');
}
});
console.log(giftPromise);
在上面的代码中,我们创建了一个 Promise,如果变量“ isPossibleToPurchaseGift ”的值设置为 true,则该 Promise 会被解析。最后,我们将该 Promise 的解析状态显示在浏览器的控制台窗口中。
如果我们仔细查看控制台窗口,可以展开 Promise 对象,然后展开如下屏幕截图所示的突出显示部分,即可得到如下屏幕截图所示的结果。
如果我们进一步放大,就会看到类似下图所示的内容。请注意图中高亮显示的部分。
Promise 对象中的静态方法
`Promise.all(promises)`:它会等待所有 Promise 都完成,并返回所有 Promise 结果的数组。需要注意的是,如果任何一个 Promise 未完成,则该 Promise 未完成即为 `Promise.all` 的错误,所有其他结果都将被忽略。
Promise.allSettled(promises):这是一个最近添加的方法。它的目的是等待所有 Promise 完成,并返回结果,结果是一个对象数组,其中包含状态(可以是“已完成”或“已拒绝”)和值(如果已完成)或原因(如果已拒绝)。
Promise.race(promises):它会等待第一个 Promise 完成,其结果或错误将成为结果。
Promise.resolve(value) : 它会生成一个具有给定值的已解决的 Promise。
Promise.reject(error) : 它会生成一个带有给定错误的被拒绝的 Promise。
在 Javascript 中创建 Promise
var isPossibleToPurchaseGift = true;
var willGetNewGift = new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
var gift = {
ring: true,
flowers: true
};
resolve(gift);
} else {
var error = new Error('Left my wallet!!');
reject(error);
}
});
在上面的代码中,我们创建了一个名为“ willGetNewGift ”的Promise。Promise构造函数接受两个参数,第一个是resolve函数,第二个是reject函数。
JavaScript 中的 Promise Resolve 是什么?
简单来说,`resolve` 函数指示 Promise 是否成功,如果成功,则 Promise 对象会被解析并赋予给定的值。因此,在上面的代码片段中,如果变量 ` willGetNewGift`设置为 `true`,则 Promise 将返回一个礼品对象。
JavaScript 中的 Promise Reject 是什么?
reject 函数会返回一个被拒绝的 Promise 对象,并附带错误消息。在上面的代码片段中,如果变量“ willGetNewGift ”设置为 false,则此 Promise 将返回一个错误对象。
在 Javascript 中调用 Promise
var getNewGift = function() {
willGetNewGift
.then(function(gift) {
console.log(gift);
})
.catch(function(error) {
console.log(error.message);
});
};
getNewGift();
在上面的代码中,我们调用了名为“ willGetNewGift ”的 Promise ,然后为了获取已完成 Promise 的值,我们使用了 `then()` 函数。我们将变量“ isPossibleToPurchaseGift ”设置为 true。如果该值为 true,则表示 Promise 已解决,因此我们可以在 `then()` 函数中显示礼品对象。完整的代码如下所示。
var isPossibleToPurchaseGift = false;
var willGetNewGift = new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
var gift = {
ring: true,
flowers: true
};
resolve(gift);
} else {
var error = new Error('Left my wallet!!');
reject(error);
}
});
var getNewGift = function() {
willGetNewGift
.then(function(gift) {
console.log(gift);
})
.catch(function(error) {
console.log(error.message);
});
};
getNewGift();
JavaScript 中的 Promise 链
非技术视角
假设你答应女朋友要给她买一份贵重的礼物,之后你还想和她一起吃顿饭,最后还想和她一起兜风。想象一下,在履行了第一个承诺之后,你还得履行第二个和第三个承诺,情况会是怎样的呢?
要处理这种情况,你需要将多个 Promise 链接在一起。因此,Promise 链在这些情况下非常有用。
技术角度
-
Promise 对象能够在 JavaScript 中执行异步任务。每个异步任务都会返回一个 Promise 对象,每个 Promise 对象都有一个 then 函数,该函数可以接受两个参数:成功处理程序和错误处理程序。
-
then函数还会返回一个 Promise,因此可以链接多个 Promise。
-
每个处理程序(成功或错误)也可以返回一个值,该值将作为参数传递给承诺链中的下一个函数。
-
如果一个处理程序返回一个 Promise,那么只有在该请求完成后才会调用下一个处理程序。
让我们用一个例子来佐证我们之前所说的。
在 Javascript 中实现 Promise 链
var isPossibleToPurchaseGift = true;
var willGetNewGift = new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
var gift = {
ring: true,
flowers: true
};
resolve(gift);
} else {
var error = new Error('Left my wallet!!');
reject(error);
}
});
var willAttendDinner = function(expensiveGift) {
return new Promise(function(resolve, reject) {
var message = 'You kept your promise by giving her an expensive ring';
resolve(message);
});
};
var willGoOnALongDrive = function(dinnerAttended) {
return new Promise(function(resolve, reject) {
var message = 'You kept your last promise by going on a long drive!';
resolve(message);
});
};
var getNewGift = function() {
willGetNewGift
.then(willAttendDinner)
.then(willGoOnALongDrive)
.then(function(longDrive) {
console.log(longDrive);
});
};
getNewGift();
在上面的代码片段中,我们定义了 3 个独立的函数,第一个函数“ willGetNewGift ”返回一个 Promise 对象,其他函数也返回 Promise。
让我详细解释一下发生了什么。首先,调用了` willGetNewGift`函数,该函数返回一个 Promise 对象。然后,这个 Promise 对象被传递给下一个函数 ` willAttendDinner`,该函数也返回一个 Promise 对象。接着,这个对象被传递给 ` willGoOnALongDrive`函数。最后,函数的返回结果会显示在控制台上。这就是为什么你会看到“你履行了上次的承诺,去长途旅行了!”这条消息的原因。
Promise.all() 是什么?
简单来说,当有多个 Promise 需要等待每个 Promise 完成后才能执行下一个 Promise 时,promise.all() 方法就很有用。
根据 MDN 文档:Promise.all() 方法返回一个 Promise,当所有作为可迭代对象传递的 Promise 都已解决,或者该可迭代对象不包含任何 Promise 时,该 Promise 才会解决。它会拒绝,拒绝原因与第一个被拒绝的 Promise 相同。
因此,文档中明确指出,如果数组中的任何一个 Promise 对象被拒绝,则整个 Promise.all() 方法都会被拒绝。
Promise.all() 的工作原理是什么?
根据 MDN 文档,我们知道 `Promise.all()` 方法接受一个可迭代对象。所谓可迭代对象,是指可以轻松迭代的对象。字符串和数组就是这类可迭代对象的例子。
通常,此方法返回一个待处理的 Promise 对象,一旦给定可迭代对象中的 Promise 被解决或拒绝,该对象就会以异步方式被解决或拒绝。
Promise 成功解析后,各个 Promise 的值将按照它们在 `promise all` 方法中传递时的顺序排列。如果可迭代对象中的任何一个 Promise 被拒绝,则所有 Promise 都会被拒绝。即使其余 Promise 都成功解析,这种情况也会发生。
在 Javascript 中实现 Promise.all()
var isPossibleToPurchaseGift = true;
var willGetNewGift = function() {
return new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
var gift = {
ring: true,
flowers: true
};
resolve('You bought an expensive ring and flowers');
} else {
var error = new Error('Left my wallet!!');
reject(error);
}
});
};
var willAttendDinner = function(expensiveGift) {
return new Promise(function(resolve, reject) {
var message = 'You kept your promise';
resolve(message);
});
};
var willGoOnALongDrive = function(dinnerAttended) {
return new Promise(function(resolve, reject) {
var message = 'You kept your last promise by going on a long drive!';
resolve(message);
});
};
var getNewGift = function() {
Promise.all([
willGetNewGift(),
willAttendDinner(),
willGoOnALongDrive()
]).then(function(result) {
console.log(result);
});
};
getNewGift();
在上面的代码片段中,我们创建了三个函数,每个函数都返回一个 Promise 对象。然后,我们在 `Promise.all()` 函数中分别调用了这三个函数,该函数返回一个数组,其中包含了所有 Promise 的结果。输出结果如下所示。
如果其中任何一个 Promise 未能解析成功,结果将产生错误。代码片段如下所示。
var isPossibleToPurchaseGift = false;
var willGetNewGift = function() {
return new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
var gift = {
ring: true,
flowers: true
};
resolve('You bought an expensive ring and flowers');
} else {
var error = new Error('Left my wallet!!');
reject(error);
}
});
};
var willAttendDinner = function(expensiveGift) {
return new Promise(function(resolve, reject) {
var message = 'You kept your promise';
resolve(message);
});
};
var willGoOnALongDrive = function(dinnerAttended) {
return new Promise(function(resolve, reject) {
var message = 'You kept your last promise by going on a long drive!';
resolve(message);
});
};
var getNewGift = function() {
Promise.all([
willGetNewGift(),
willAttendDinner(),
willGoOnALongDrive()
]).then(function(result) {
console.log(result);
}).catch(function(error){
console.log(error.message);
});
};
getNewGift();
代码的输出结果如下所示。
Promise.race() 是什么?
如果我们需要在第一个已解决的 Promise 或已拒绝的 Promise 的结果可用时立即返回该结果,那么我们应该使用此函数。
根据 MDN 文档,Promise.race() 方法返回一个 Promise,该 Promise 会在可迭代对象中的一个 Promise 完成或拒绝时立即完成或拒绝,并返回该 Promise 的值或原因。
在 Javascript 中实现 Promise.race()
var isPossibleToPurchaseGift = true;
var willGetNewGift = function() {
return new Promise(function(resolve, reject) {
if(isPossibleToPurchaseGift) {
var gift = {
ring: true,
flowers: true
};
setTimeout(function(){
resolve('You bought an expensive ring and flowers');
}, 500);
} else {
var error = new Error('Left my wallet!!');
reject(error);
}
});
};
var willAttendDinner = function(expensiveGift) {
return new Promise(function(resolve, reject) {
var message = 'You kept your promise';
setTimeout(function(){
resolve(message);
}, 2000);
});
};
var willGoOnALongDrive = function(dinnerAttended) {
return new Promise(function(resolve, reject) {
var message = 'You kept your last promise by going on a long drive!';
setTimeout(function(){
resolve(message);
},3000);
});
};
var getNewGift = function() {
Promise.race([
willGetNewGift(),
willAttendDinner(),
willGoOnALongDrive()
]).then(function(result) {
console.log(result);
}).catch(function(error){
console.log(error.message);
});
};
getNewGift();
在上面的代码片段中,我们可以看到,在三个成功执行后返回 Promise 对象的函数中,只有`willGetNewGift()`函数的执行耗时 500 毫秒。因此,运行此代码块后,该 Promise 的结果就会返回。
JavaScript Promise 是同步的还是异步的?
首先,你应该知道 JavaScript 是一种单线程脚本语言。单线程意味着它必须先执行完一个代码块才能执行下一个代码块。简单来说,JavaScript 代码本质上是阻塞式的。
有时我们需要执行一些任务,但我们无法确定任务何时完成以及何时返回结果。但与此同时,我们需要确保在任务成功时必须执行某些代码块,或者在任务失败时也需要处理相应的情况。
为了解决这些问题,我们需要在 JavaScript 中编写异步代码。Promise 允许我们以异步方式编写代码。因此,显然我们可以说 Promise 是异步的。
让我们用一个例子来证明 Promise 是异步的。
var isPossibleToPurchaseGift = true;
// willGetNewGift promise definition
// willAttendDinner promise definition
// willGoOnALongDrive promise definition
var getNewGift = function() {
console.log('Before giving gift');
willGetNewGift
.then(willAttendDinner)
.then(willGoOnALongDrive)
.then(function(longDrive) {
console.log(longDrive);
});
console.log('After giving gift');
};
// call our promise
getNewGift();
您可能期望得到以下输出。
- 送礼物之前
- 你信守了最后一个承诺,进行了一次长途自驾游!
- 送完礼物后
但实际输出结果如下面的屏幕截图所示。
以更简洁的方式实现 JavaScript Promise
本文中的所有示例都使用了 Promise 包装器的语法。我们使用这种语法是为了方便您理解 Promise,但实际上我们可以用更简洁的方式来编写 Promise。如果我们采用更简洁的方式来编写 Promise,维护复杂任务的 Promise 将会容易得多。
让我解释一下什么是 Promise 包装器。在 Promise 包装器中,你需要编写代码来根据 Promise 是否成功执行来解析或拒绝该 Promise。
return new Promise(function(resolve, reject){
// codes to execute
});
以上代码片段是 Promise 包装器的示例。
以下代码片段说明了如何更好地编写 Promise。
var isPossibleToPurchaseGift = true;
//var isPossibleToPurchaseGift = false;
var willGetNewGift = function() {
if(isPossibleToPurchaseGift) {
return Promise.resolve('It is possible to purchase gift');
} else {
var error = new Error('Left my wallet!!');
return Promise.reject(error);
}
};
var willAttendDinner = function(purchasedGift) {
// purchasedGift = false;
if(purchasedGift) {
return Promise.resolve('It is possible to attend dinner');
} else {
return Promise.reject(new Error('Unable to attend dinner!!'));
}
};
var willGoOnALongDrive = function(attendedDinner) {
// attendedDinner = false;
if(attendedDinner) {
return Promise.resolve('It is possible to go on a long drive');
} else {
return Promise.reject(new Error('Unable to go on a long drive!!'));
}
};
willGetNewGift()
.then(willAttendDinner)
.then(willGoOnALongDrive)
.then(function(response){
console.log(response);
}).catch(function(error){
console.log(error.message);
});
尝试逐条取消注释,然后再次运行代码。我相信你很快就能明白其中的区别。
使用 ES6/ES2015、ES7 编写 JavaScript Promise
ES6 或 ES2015 引入了 `let`、`const` 和 `fat arrow` 语法。使用这些语法,你可以更好地编写 Promise。
我们可以用 ES6 更好地重写前面的例子。代码片段如下所示。
const isPossibleToPurchaseGift = true;
// const isPossibleToPurchaseGift = false;
var willGetNewGift = ()=> {
if(isPossibleToPurchaseGift) {
return Promise.resolve('It is possible to purchase gift');
} else {
const error = new Error('Left my wallet!!');
return Promise.reject(error);
}
};
var willAttendDinner = (purchasedGift)=> {
// purchasedGift = false;
if(purchasedGift) {
return Promise.resolve('It is possible to attend dinner');
} else {
return Promise.reject(new Error('Unable to attend dinner!!'));
}
};
var willGoOnALongDrive = (attendedDinner) => {
// attendedDinner = false;
if(attendedDinner) {
return Promise.resolve('It is possible to go on a long drive');
} else {
return Promise.reject(new Error('Unable to go on a long drive!!'));
}
};
willGetNewGift()
.then(willAttendDinner)
.then(willGoOnALongDrive)
.then(response =>console.log(response))
.catch(error =>console.log(error.message));
如果取消注释掉被注释的行,就可以更好地对代码片段进行操作。
ES7 引入了 async 和 await 语法。将其应用到我们的 ES6 代码后,会更容易理解。此外,我们也不再需要使用 then 和 catch 函数。对于错误处理,您需要使用 JavaScript 的 try...catch 语法。
const isPossibleToPurchaseGift = true;
// const isPossibleToPurchaseGift = false;
var willGetNewGift = ()=> {
if(isPossibleToPurchaseGift) {
return Promise.resolve('It is possible to purchase gift');
} else {
const error = new Error('Left my wallet!!');
return Promise.reject(error);
}
};
var willAttendDinner = (purchasedGift)=> {
// purchasedGift = false;
if(purchasedGift) {
return Promise.resolve('It is possible to attend dinner');
} else {
return Promise.reject(new Error('Unable to attend dinner!!'));
}
};
var willGoOnALongDrive = (attendedDinner) => {
// attendedDinner = false;
if(attendedDinner) {
return Promise.resolve('It is possible to go on a long drive');
} else {
return Promise.reject(new Error('Unable to go on a long drive!!'));
}
};
async function callFunctions() {
try {
willGetGift = await willGetNewGift();
attendDinner = await willAttendDinner(willGetGift);
willGoOnALongDrive = await willGoOnALongDrive(attendDinner);
console.log(willGoOnALongDrive);
} catch(error) {
console.log(error.message);
}
}
callFunctions();
为了更好地理解代码,我建议你一次取消注释一段代码。这样你就能更好地理解了。
结论
希望您读完本文后能深入了解 JavaScript Promise。如果您觉得本文有用,请不要忘记分享给其他人。谢谢!
文章来源:https://dev.to/nileshsanyal/promises-in-javascript-a-complete-guide-for-2019-4o99






