Javascript utilise des fonctions de rappel pour gérer la programmation asynchrone. Il existe un processus d'adaptation de la programmation synchrone à la programmation de rappel asynchrone, mais s'il existe plusieurs niveaux d'imbrication de rappel, ce que nous appelons souvent la pyramide de rappel de Doom (Pyramid of Doom), c'est certainement une mauvaise expérience de programmation. Il existe donc la spécification Promises/A de CommonJS pour résoudre le problème de la pyramide de rappel. Cet article présente d'abord les spécifications liées aux promesses, puis approfondit la compréhension en interprétant une mini promesse.
Qu'est-ce que la promesse
Un objet Promise représente une valeur qui n'est pas disponible actuellement, mais qui peut être résolue à un moment donné dans le futur. Il vous permet d'écrire du code asynchrone de manière synchrone. Par exemple, si vous souhaitez utiliser l'API Promise pour effectuer un appel asynchrone vers un serveur distant, vous devez créer un objet Promise qui représente les données qui seront renvoyées par le service Web à l'avenir. Le seul problème est que les données ne sont pas encore disponibles. Les données deviendront disponibles lorsque la demande sera terminée et renvoyée par le serveur. Pendant ce temps, l'objet Promise agira comme un proxy pour les données réelles. Ensuite, vous pouvez lier une fonction de rappel à l'objet Promise, qui sera appelée une fois que les données réelles seront disponibles.
Les objets Promise ont existé dans de nombreux langages sous de nombreuses formes.
Supprimez la pyramide du malheur
L'anti-modèle le plus courant en Javascript consiste à imbriquer les rappels dans les rappels.
// 回调金字塔 asyncOperation(function(data){ // 处理 `data` anotherAsync(function(data2){ // 处理 `data2` yetAnotherAsync(function(){ // 完成 }); }); });
Code après avoir introduit les promesses
promiseSomething() .then(function(data){ // 处理 `data` return anotherAsync(); }) .then(function(data2){ // 处理 `data2` return yetAnotherAsync(); }) .then(function(){ // 完成 });
Les promesses transforment les rappels imbriqués en une série d'appels .then consécutifs, éliminant le mauvais style de code des couches d'indentation. Les promesses ne sont pas un algorithme pour résoudre un problème spécifique, mais un meilleur modèle d'organisation du code. Tout en acceptant le nouveau modèle organisationnel, nous comprenons progressivement les appels asynchrones sous un nouvel angle.
Chaque plateforme linguistique a une implémentation de promesse correspondante
Permettez-moi maintenant de jeter un œil à quelques détails de chaque spécification dans l'environnement du langage javascript.
Promesses/Un cahier des charges
promise représente une valeur finale qui est renvoyée lorsqu'une opération est terminée.
Promise a une méthode then, qui peut accepter 3 fonctions comme paramètres. Les deux premières fonctions correspondent aux fonctions de rappel des deux états de promesse : remplie et rejetée. La troisième fonction gère les informations de progression (la prise en charge des rappels de progression est facultative).
promiseSomething().then(function(fulfilled){ //当promise状态变成fulfilled时,调用此函数 },function(rejected){ //当promise状态变成rejected时,调用此函数 },function(progress){ //当返回进度信息时,调用此函数 });
如果 promise 支持如下连个附加方法,称之为可交互的 promise
获得当前 promise 最终值上的一个属性,返回值是一个新的 promise。
调用当然 promise 最终值上的一个方法,返回值也是一个新的promise。
Promises/B 规范
在 Promises/A 的基础上,Promises/B 定义了一组 promise 模块需要实现的 API
when(value, callback, errback_opt)
如果 value 不是一个 promise ,那么下一事件循环callback会被调用,value 作为 callback 的传入值。如果 value 是一个 promise,promise 的状态已经完成或者变成完成时,那么下一事件循环 callback 会被调用,resolve 的值会被传入 callback;promise 的状态已经失败或者变成失败时,那么下一事件循环 errback 会被调用,reason 会作为失败的理由传入 errback。
asap(value, callback, errback_opt)
与 when 最大的区别,如果 value 不是一个 promise,会被立即执行,不会等到下一事件循环。
enqueue(task Function)
尽可能快地在接下来的事件循环调用 task 方法。
get(object, name)
返回一个获得对象属性的 promise。
post(object, name, args)
返回一个调用对象方法的 promise。
put(object, name, value)
返回一个修改对象属性的 promise。
del(object, name)
返回一个删除对象属性的 promise。
makePromise(descriptor Object, fallback Function)
返回一个 promise 对象,该对象必须是一个可调用的函数,也可能是可被实例化的构造函数。
{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, }
上面每一个注册的 handle 都返回一个 resolved value或者 promise。
defer()
返回一个对象,该对象包含一个 resolve(value) 方法和一个 promise 属性。
当 resolve(value) 方法被第一次调用时,promise 属性的状态变成 完成,所有之前或之后观察该 promise 的 promise 的状态都被转变成 完成。value 参数如果不是一个 promise ,会被包装成一个 promise 的 ref。resolve 方法会忽略之后的所有调用。
reject(reason String)
返回一个被标记为 失败 的 promise。
一个失败的 promise 上被调用 when(message) 方法时,会采用如下两种方法之一
1. 如果存在 errback,errback 会以 reason 作为参数被调用。when方法会将 errback 的返回值返回。
2. 如果不存在 errback,when 方法返回一个新的 reject 状态的promise 对象,以同一 reason 作为参数。
ref(value)
如果 value 是 promise 对象,返回 value 本身。否则,返回一个resolved 的 promise,携带如下 handle。
1. when(errback),忽略 errback,返回 resolved 值
2. get(name),返回 resolved 值的对应属性。
3. put(name, value) ,设置 resolved 值的对应属性。
4. del(name),删除 resolved 值的对应属性。
5. post(name, args), 调用 resolved 值的对应方法。
6. 其他所有的调用都返回一个 reject,并携带 "Promise does not handle NAME" 的理由。
isPromise(value) Boolean
判断一个对象是否是 promise
method(name String)
获得一个返回 name 对应方法的 promise。返回值是 "get", "put", "del" 和 "post" 对应的方法,但是会在下一事件循环返回。
Promises/D 规范
为了增加不同 promise 实现之间的可互操作性,Promises/D 规范对promise 对象和 Promises/B 规范做了进一步的约定。以达到鸭子类型的效果(Duck-type Promise)。
简单来说Promises/D 规范,做了两件事情,
1、如何判断一个对象是 Promise 类型。
2、对 Promises/B 规范进行细节补充。
甄别一个 Promise 对象
Promise 对象必须是实现 promiseSend 方法。
1. 在 promise 库上下文中,如果对象包含 promiseSend 方法就可以甄别为promise 对象
2. promiseSend 方法必须接受一个操作名称,作为第一个参数
3. 操作名称是一个可扩展的集合,下面是一些保留名称
1. when,此时第三个参数必须是 rejection 回调。
1. rejection回调必须接受一个 rejection 原因(可以是任何值)作为第一个参数
2. get,此时第三个参数为属性名(字符串类型)
3. put,此时第三个参数为属性名(字符串类型),第四个参数为新属性值。
4. del,此时第三个参数为属性名
5. post,此时第三个参数为方法的属性名,接下来的变参为方法的调用参数
6. isDef
4. promiseSend方法的第二个参数为 resolver 方法
5. promiseSend方法可能接受变参
6. promiseSend方法必须返回undefined
对 Promises/B 规范的补充
Promises/D 规范中对 Promises/B 规范中定义的ref、reject、def、defer方法做了进一步细致的约束,此处略去这些细节。
Promises/A+ 规范
前面提到的 Promises/A/B/D 规范都是有CommonJS组织提出的,Promises/A+是有一个自称为Promises/A+ 组织发布的,该规范是以Promises/A作为基础进行补充和修订,旨在提高promise实现之间的可互操作性。
Promises/A+ 对.then方法进行细致的补充,定义了细致的Promise Resolution Procedure流程,并且将.then方法作为promise的对象甄别方法。
此外,Promises/A+ 还提供了兼容性测试工具,以确定各个实现的兼容性。
实现一个迷你版本的Promise
上面扯了这么多规范,现在我们看看如何实现一个简单而短小的Promise。
1、状态机
var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise() { // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value or error once FULFILLED or REJECTED var value = null; // store sucess & failure handlers attached by calling .then or .done var handlers = []; }
2、状态变迁
仅支持两种状态变迁,fulfill和reject
// ... function Promise() { // ... function fulfill(result) { state = FULFILLED; value = result; } function reject(error) { state = REJECTED; value = error; } }
fulfill和reject方法较为底层,通常更高级的resolve方法开放给外部。
// ... function Promise() { // ... function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); } } }
resolve方法可以接受一个普通值或者另一个promise作为参数,如果接受一个promise作为参数,等待其完成。promise不允许被另一个promise fulfill,所以需要开放resolve方法。resolve方法依赖一些帮助方法定义如下:
/** * Check if a value is a Promise and, if it is, * return the `then` method of that promise. * * @param {Promise|Any} value * @return {Function|Null} */ function getThen(value) { var t = typeof value; if (value && (t === 'object' || t === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. * * @param {Function} fn A resolver function that may not be trusted * @param {Function} onFulfilled * @param {Function} onRejected */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function (value) { if (done) return done = true onFulfilled(value) }, function (reason) { if (done) return done = true onRejected(reason) }) } catch (ex) { if (done) return done = true onRejected(ex) } }
这里resolve和doResolve之间的递归很巧妙,用来处理promise的层层嵌套(promise的value是一个promise)。
构造器
// ... function Promise(fn) { // ... doResolve(fn, resolve, reject); }
.done方法
// ... function Promise(fn) { // ... function handle(handler) { if (state === PENDING) { handlers.push(handler); } else { if (state === FULFILLED && typeof handler.onFulfilled === 'function') { handler.onFulfilled(value); } if (state === REJECTED && typeof handler.onRejected === 'function') { handler.onRejected(value); } } } this.done = function (onFulfilled, onRejected) { // ensure we are always asynchronous setTimeout(function () { handle({ onFulfilled: onFulfilled, onRejected: onRejected }); }, 0); } // ... }
.then方法
// ... function Promise(fn) { // ... this.then = function (onFulfilled, onRejected) { var self = this; return new Promise(function (resolve, reject) { return self.done(function (result) { if (typeof onFulfilled === 'function') { try { return resolve(onFulfilled(result)); } catch (ex) { return reject(ex); } } else { return resolve(result); } }, function (error) { if (typeof onRejected === 'function') { try { return resolve(onRejected(error)); } catch (ex) { return reject(ex); } } else { return reject(error); } }); }); } // ... }
$.promise
jQuery 1.8 之前的版本,jQuery的 then 方法只是一种可以同时调用 done 、fail 和 progress 这三种回调的速写方法,而 Promises/A 规范的 then 在行为上更像是 jQuery 的 pipe。 jQuery 1.8 修正了这个问题,使 then 成为 pipe 的同义词。不过,由于向后兼容的问题,jQuery 的 Promise 再如何对 Promises/A 示好也不太会招人待见。
此外,在 Promises/A 规范中,由 then 方法生成的 Promise 对象是已执行还是已拒绝,取决于由 then 方法调用的那个回调是返回值还是抛出错误。在 JQuery 的 Promise 对象的回调中抛出错误是个糟糕的主意,因为错误不会被捕获。
小结
最后一个例子揭示了,实现 Promise 的关键是实现好 doResolve 方法,在完事以后触发回调。而为了保证异步 setTimeout(fun, 0); 是关键一步。
Promise 一直用得蛮顺手的,其很好的优化了 NodeJS 异步处理时的代码结构。但是对于其工作原理却有些懵懂和好奇,于是花了些精力查阅并翻译了Promise 的规范,以充分的理解 Promise 的细节。
以上就是关于JavaScript中Promise的使用方法介绍,希望对大家的学习有所帮助。