为什么需要异步编程方式

一个函数执行之后,在它后面顺序编写的代码中,如果能够直接使用它的返回结果或者它修改之后的引用参数,那么我们通常认为该函数是同步的。

如果一个函数的执行结果或者其修改的引用参数,需要通过设置回调函数或者回调事件的方式来获取,而在其后顺序编写的代码中无法直接获取的话,那么我们通常认为这样的函数是异步的。

阻塞带来的问题是当前线程(或进程)会陷入等待,一直等到阻塞结束,这样就会造成线程(或进程)资源的浪费。所以通常认为阻塞是不够高效的。

如果要编写非阻塞代码,使用同步方式会变得有些复杂,且不够灵活。同步方式的非阻塞代码通常会使用 select 模式,例如 curl_multi_select、stream_select、socket_select等就是PHP中提供的一些典型的 select 模式的函数。我们说它复杂且不够灵活,是因为例如使用上面的 select 模式编写同步的非阻塞代码时我们需要先构造一个并发任务的列表,之后手动构造循环来执行这些并发的任务,在循环开始之后,虽然这几个任务可以并发,但是这个循环相对于其后的代码总体上仍然是阻塞的,我们要想拿到这些并发任务的结果时,仍然需要等待。select 虽然可以同时等待多个任务中某一个或几个就位后,再执行后续操作,但仍然有一部分时间是被等待消耗掉的。而且如果是纯同步非阻塞的情况下,我们也很难在循环开始后,动态添加更多的任务到这个循环中去。所以希望程序能够更加高效、更加灵活,就需要引入异步方式

传统的异步方式有什么问题

回调是最简单直接的异步模式,只要在调用异步函数时设置一个或多个回调函数,函数就会在完成时自动调用回调函数。如果你的程序逻辑够简单,简单的一两层回调也许并不会让你觉得异步方式的编程有什么麻烦。但如果你的程序逻辑一旦有些复杂,你可能就会被层层回调搞得疲惫不堪了。当然,实际上你的程序需要层层回调的原因,也许并不是你的程序逻辑真的复杂,而是你没有办法将回调函数中的参数结果传出来,所以你就不得不将另一个回调函数传进去

为什么要引入Promise

Promise要解决的问题是,如何将回调方法的参数从回调方法中传递出来,让它可以像同步函数的返回结果一样,在回调函数以外的控制范围内,可以传递和复用。

Promise是抽象异步处理对象以及对其进行各种操作的组件

Promises/A+规范

  • 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但是在后世的 promise 实现多以 resolve 来取代之。
  • 拒绝(reject):指一个 promise 失败时进行的一系列操作。
  • 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
  • 拒因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。

Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 Promise 的终值或本 Promise 不能执行的原因。

术语:

  • Promise:它是一个拥有 then 方法的对象或函数,其行为符合本规范;
  • thenable:是一个定义了 then 方法的对象或函数,即拥有 then 方法;
  • 值(value):指任何 JavaScript 的合法值(包括 undefined、thenable 和 promise);
  • 异常(exception):是使用 throw 语句抛出的一个值;
  • 拒因(reason):表示一个 promise 的拒绝原因;

要求:

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

等待态(Pending)

处于等待态时,promise 需要满足以下条件:

  • 可以迁移至执行态或拒绝态

执行态(Fulfilled)

处于执行态时,promise 需要满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected)

处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的拒因

这里的不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变(指当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)

Then 方法

一个 promise 必须提供一个 then 方法以访问其当前值、终值和拒因。

promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

参数可选

onFulfilled 和 onRejected 都是可选参数。

  • 如果 onFulfilled  不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

onFulfilled 特性

如果 onFulfilled 是函数:

  • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • 在 promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特性

如果onRejected 是函数:

  • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的拒因
  • 在 promise 被拒绝执行前其不可被调用
  • 其调用次数不可超过一次

调用时机

onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用(平台代码:指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。这个事件队列可以采用"宏任务macrotask"机制或"微任务microtask"机制来实现。由于 promise 的实施代码本身就是平台代码(即都是JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列。)

macrotask 和 microtask 两个概念,这表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

两个类别的具体分类如下:

  • macro-task:script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI rendering
  • micro-task:process.nextTick、Promises(浏览器原生的Promises)、Object.observe、MutationObserver

调用要求

onFulfilled 和 onRejected 必须被作为函数调用(即没有 this 值)

多次调用

then 方法可以被同一个 promise 调用多次

  • 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
  • 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

返回

then 方法必须返回一个 promise 对象

promise2 = promise1.then(onFulfilled, onRejected);

  

  • 如果 onFulfilled 或者 onRejected 返回一个值 x,则运行下面的 Promise 解决过程:[ [ Resolve ] ] (promise2,x)
  • 如果 onFulfilled 或者 onRejected 抛出一个异常 e,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulfilled 不是函数且 promise1 成功执行,promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行,promise2 必须拒绝执行并返回相同的拒因

不论 promise1 被 reject 还是被 resolve 时 promise2 都会被 resolve,只有出现异常时才会被 rejected。

Promise 解决过程

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [ [ Resolve ] ] (promise,x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise。

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [ [ Resolve ] ] (promise,x) 需要遵循以下步骤:

x 与 promise 相等

如果 promise 和 x 指向同一个对象,以 TypeError 为拒因拒绝执行 promise

x 为 promise

如果 x 为 Promise,则使 promise 接受 x 的状态:

  • 如果 x 处于等待态,promise 需要保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态,用相同的值执行 promise
  • 如果 x 处于拒绝态,用相同的拒因拒绝 promise

x 为对象或函数

如果 x 为对象或函数:

  • 把 x.then 赋值给 then
  • 如果取 x.then 的值时抛出错误 e,则以 e 为拒因拒绝 promise
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise:
    • 如果 resolvePromise 以值 y 为参数被调用,则运行 [ [ Resolve ] ](promise,y)
    • 如果 rejectPromise 以拒因 r 为参数被调用,则以拒因 r 拒绝 promise
    • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    • 如果调用 then 方法抛出了异常 e :
      • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 否则以 e 为拒因拒绝 promise
    • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [ [Resolve] ](promise,thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为拒因来拒绝 promise。

Promises/A+规范的更多相关文章

  1. JavaScript中Promises/A+规范的实现

    Promises是一种异步编程模型,通过一组API来规范化异步操作,这样也能够让异步操作的流程控制更加容易. 下面的代码是假设执行一个异步队列,每一项都会使用上一项返回的数据: function ne ...

  2. CommonJS Promises/A规范

    本文来自四火哥的翻译 CommonJS是一组javascript编程规范,而promise是其中之一. 简而言之,promises是一种令代码的异步行为变得更加优雅的软件抽象.在基本的定义中,代码可能 ...

  3. JS魔法堂:剖析源码理解Promises/A规范

    一.前言 Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现 如Q, Bluebird, when, rsvp. ...

  4. 前端翻译:Promises/A+规范

    原文地址:https://promisesaplus.com/ 本篇为原文翻译+个人理解,若有谬误请各位指正,谢谢. 尊重原创,转载请注明来自:http://www.cnblogs.com/fsjoh ...

  5. 一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise

    本文是一起学习造轮子系列的第一篇,本篇我们将从零开始写一个符合Promises/A+规范的promise,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Pr ...

  6. 对 Promises/A+ 规范的研究 ------引用

    作为 Modern JavaScript 基础设施的一部分,Promises 对前端开发者而言异常重要.它是 async/await 语法的基础,是 JavaScript 中处理异步的标准形式.并且, ...

  7. 【翻译】Promises/A+规范

    目录 介绍 译文 1. 术语(Terminology) 2. 要求(Requirements) 2.1 Promise状态 2.2 then方法 2.3 Promise解析程序 3. 注释 3.1 p ...

  8. 异步编程之Javascript Promises 规范介绍

    什么是 Promises Promises是一种关于异步编程的规范,目的是将异步处理对象和处理规则进行规范化,为异步编程提供统一接口. 传统的回调函数 说到JavaScript的异步编程处理,通常我们 ...

  9. node.js下when.js(Promises/A)的实践

    假设一个业务场景: 通过rss地址,获取rss并保存于文件,rss地址保存于文件中. 完成该场景的业务需要完成3个任务: 1.从文件中读取rss地址. 2.获取rss. 3.保存于文件. 最后将这三个 ...

随机推荐

  1. css3 压缩及验证工具

    1.css w3c统一验证工具 网址:http://www.csstats.com/ 如果你想要更全面的,这个神奇,你值得拥有: w3c统一验证工具:http://validator.w3.org/u ...

  2. Oracle数据库sqlldr工具的使用

    sqlldr导入文本内容到数据库表时,需要指定一个ctl文件(控制文件),通过该文件来完成数据的导入. 1 首先创建一个表student create table student( stu_id nu ...

  3. Spring学习(十)Spring知识点汇总

    一.基础概念 Q:Spring是什么? 定义:Spring是一个轻量级的IoC(控制反转)和AOP容器框架. 目的:用于简化企业应用程序的开发,使得开发者只需要关心业务需求. 常见的配置方式: 基于X ...

  4. Flutter集成环信IM,发送图片之后渲染conversation.loadMoreMsgFromDB报path为空

    这时会报错,结果如下: 只需在em_message_body下修改如图path为空即可

  5. 手把手教你AspNetCore WebApi:入门

    需求 前几天,马老板给小明和小红一个"待办事项"网站,小明负责后端,小红负责前端,并要求网站可以同时在 Windows.和 Linux 上运行. 小明整理了一下"待办事项 ...

  6. 076 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 01 Java面向对象导学

    076 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 01 Java面向对象导学 本文知识点:Java面向对象导学 说明:因为时间紧张,本人 ...

  7. C++ 中explicit的作用

    转载:https://www.cnblogs.com/diligenceday/p/5781408.html C++ 中explicit的作用   explicit作用: 在C++中,explicit ...

  8. [POI2009]ARC-Architects

    [POI2009]ARC-Architects 题意: 给定一个序列,从中挑选k个数,满足下标单调递增,并且字典序最小: 思路: 由于字典序最小,所以考虑贪心,即前面的数尽可能大,所以用单调队列维护最 ...

  9. 轻轻松松学CSS:Flex布局

     Flex布局就是"弹性布局",它可以简便.完整.响应式地实现各种页面布局.引入弹性布局的目的,当页面需要适应不同的屏幕大小确保元素拥有恰当的布局方式,对一个容器中的子元素进行排列 ...

  10. 关于IPA文件重签名后如何跟踪管理APP的技术探讨和实践演示

    前言:开发iOS的朋友都知道,在功能开发完成后,我们就会用申请的苹果账号在后台做证书配置,然后提交到AppStore,但是也有部分APP我们不需要提交到AppStore,比如内部测试用的APP.定制给 ...