理解JavaScript中Promise的使用_javascript技巧

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题。本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解。

什么是 Promise
一个 Promise 对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。它允许你以一种同步的方式编写异步代码。例如,如果你想要使用 Promise API 异步调用一个远程的服务器,你需要创建一个代表数据将会在未来由 Web 服务返回的 Promise 对象。唯一的问题是目前数据还不可用。当请求完成并从服务器返回时数据将变为可用数据。在此期间,Promise 对象将扮演一个真实数据的代理角色。接下来,你可以在 Promise 对象上绑定一个回调函数,一旦真实数据变得可用这个回调函数将会被调用。

Promise 对象曾经以多种形式存在于许多语言中。

去除厄运的回调金字塔(Pyramid of Doom)
Javascript 中最常见的反模式做法是回调内部再嵌套回调。

// 回调金字塔
asyncOperation(function(data){
 // 处理 `data`
 anotherAsync(function(data2){
   // 处理 `data2`
   yetAnotherAsync(function(){
     // 完成
   });
 });
});

引入 Promises 之后的代码

promiseSomething()
.then(function(data){
  // 处理 `data`
  return anotherAsync();
})
.then(function(data2){
  // 处理 `data2`
  return yetAnotherAsync();
})
.then(function(){
  // 完成
});

Promises 将嵌套的 callback,改造成一系列的.then的连缀调用,去除了层层缩进的糟糕代码风格。Promises 不是一种解决具体问题的算法,而已一种更好的代码组织模式。接受新的组织模式同时,也逐渐以全新的视角来理解异步调用。

各个语言平台都有相应的 Promise 实现

  • Java's java.util.concurrent.Future
  • Python's Twisted deferreds and PEP-3148 futures
  • F#'s Async
  • .Net's Task
  • C++ 11's std::future
  • Dart's Future
  • Javascript's Promises/A/B/D/A+

下面我来相信了解一下 javascript 语言环境下各个规范的一些细节。

Promises/A 规范
promise 表示一个最终值,该值由一个操作完成时返回。

  • promise 有三种状态:**未完成** (unfulfilled),**完成** (fulfilled) 和**失败** (failed)。
  • promise 的状态只能由**未完成**转换成完成,或者**未完成**转换成**失败** 。
  • promise 的状态转换只发生一次。

promise 有一个 then 方法,then 方法可以接受 3 个函数作为参数。前两个函数对应 promise 的两种状态 fulfilled 和 rejected 的回调函数。第三个函数用于处理进度信息(对进度回调的支持是可选的)。

promiseSomething().then(function(fulfilled){
    //当promise状态变成fulfilled时,调用此函数
  },function(rejected){
    //当promise状态变成rejected时,调用此函数
  },function(progress){
    //当返回进度信息时,调用此函数
  });

如果 promise 支持如下连个附加方法,称之为可交互的 promise

  • get(propertyName)

获得当前 promise 最终值上的一个属性,返回值是一个新的 promise。

  • call(functionName, arg1, arg2, ...)

调用当然 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。

  • 第二个参数接受一个 fallback(message,...args) 函数,当没有 promise 对象没有找到对应的 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的使用方法介绍,希望对大家的学习有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索javascript
promise
javascript promise、javascript中promise、彻底理解promise、es6 promise 使用案例、nodejs promise使用,以便于您获取更多的相关知识。

时间: 2024-10-14 18:59:32

理解JavaScript中Promise的使用_javascript技巧的相关文章

理解JAVASCRIPT中hasOwnProperty()的作用_javascript技巧

JavaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性. hasOwnProperty()使用方法: object.hasOwnProperty(proName)其中参数object是必选项.一个对象的实例.proName是必选项.一个属性名称的字符串值. 如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true:反之则返回 false.此方法无法检查该对象的原型链中是否具有该属性

5分钟理解JavaScript中this用法分享_javascript技巧

前言关于JavaScript中this的用法网络中已经有较多比较详尽的介绍,可以参考本文的参考学习资料和网络.本文结合网络收集整理,尝试以一种简易的方式阐述JavaScript中this的用法,希望对大家关于JavaScript中this用法的快速理解有所帮助.正文1. this用法实例 复制代码 代码如下: window.color = "red"; var o = { color: "blue" }; function sayColor(){     alert

理解javascript中的MVC模式_javascript技巧

MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程序的业务逻辑相关的数据以及对数据处理的方法.模型有对数据直接访问的权利.模型不依赖 "视图" 和 "控制器", 也就是说 模型它不关心页面如何显示及如何被操作. 视图:视图层最主要的是监听模型层上的数据改变,并且实时的更新html页面.当然也包括一些事件的注册或者ajax请求操作(发布事件),都是放在视图

理解javascript中的with关键字_javascript技巧

说起js中的with关键字,很多小伙伴们的第一印象可能就是with关键字的作用在于改变作用域,然后最关键的一点是不推荐使用with关键字.听到不推荐with关键字后,我们很多人都会忽略掉with关键字,认为不要去管它用它就可以了.但是有时候,我们在看一些代码或者面试题的时候,其中会有with关键字的相关问题,很多坑是你没接触过的,所以还是有必要说说with这一个关键字. 一.基本说明 在js高级程序设计中是这样描述with关键字的:with语句的作用是将代码的作用域设置到一个特定的作用域中,基本

理解javascript中Map代替循环_javascript技巧

本文介绍了map给我们的js编程带来的好处及便利:1.Map能干什么map可以实现for循环的功能: <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <script> var arr = ['val1', 'val2',

深入理解javascript中defer的作用_javascript技巧

很多人都已经把 Javascript的用的炉火纯青了,但见到defer未必就知道他是做什么用的:很多人也都遇到过这样的问题,需要直接执行别且操作DOM对象的js 总是报找不到对象的错误,原因大家也都知道就是页面还有没有加载完毕,js的操作对象还在下载中.但很多人都不知道,添加defer标签就能轻而易举的解决这个问题. <script src="../CGI-bin/delscript.js" defer></script> 中的defer作用是文档加载完毕了再执

深入理解Javascript中的循环优化_javascript技巧

循环是大多数编程语言都具备的基本功能,JS也不例外,不同之处在于JS是解释型语言,运行于浏览器环境中,客户端的软硬件条件会对JS执行效率产生很大的影响.然而客户端环境对于开发者是未知.多样的,并且难以改变,所以优化代码质量是提高代码效率的主要途径.JS代码中,循环是比较容易导致性能问题的因素.理解循环特性进而有针对性地进行优化也许会带来不错的性能提升.for.while.do-while循环:这三种循环本身的循环效率相差不多,所以只要根据适合的应用场景选择即可.以for循环为例: 复制代码 代码

图解JavaScript中的this关键字_javascript技巧

JavaScript 是一种脚本语言,支持函数式编程.闭包.基于原型的继承等高级功能.JavaScript一开始看起来感觉会很容易入门,但是随着使用的深入,你会发现JavaScript其实很难掌握,有些基本概念让人匪夷所思.其中JavaScript 中的 this 关键字,就是一个比较容易混乱的概念,在不同的场景下,this会化身不同的对象.有一种观点认为,只有正确掌握了 JavaScript 中的 this 关键字,才算是迈入了 JavaScript 这门语言的门槛.在主流的面向对象的语言中(

理解Javascript图片预加载_javascript技巧

预加载图片是提高用户体验的一个很好方法.图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度.这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速.无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验.本文将分享三个不同的预加载技术,来增强网站的性能与可用性. 方法一:用CSS和JavaScript实现预加载 实现预加载图片有很多方法,包括使用CSS.JavaScript及两者的各种组合.这些技术可根据不同设计场景设计出相应的解决方案,十分高效.