Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >深入理解 Promise 之手把手教你写一版

深入理解 Promise 之手把手教你写一版

作者头像
用户1097444
发布于 2022-06-29 06:10:16
发布于 2022-06-29 06:10:16
53800
代码可运行
举报
运行总次数:0
代码可运行

什么是 Promise?

  • 语法上:Promise 是一个构造函数,返回一个带有状态的对象
  • 功能上:Promise 用于解决异步函数并根据结果做出不同的应对
  • 规范上:Promise 是一个拥有 then 方法的对象(在 JS 里函数也是对象)

为什么要用 Promise?

前端最令人头疼的事情之一就是处理异步请求:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function load() {
    $.ajax({
        url: 'xxx.com',
        data: 'jsonp',
        success: function(res) {
            init(res, function(res) {
                render(res, function(res) {
                    // 千层饼
                });
            });
        }
    }
}

代码层级多,可读性差且难以维护,形成回调地狱。

有了 Promise,我们可以用同步操作的流程写异步操作,解决了层层嵌套的回调函数的困扰:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new Promise(
  function (resolve, reject) {
    // 一段耗时的异步操作
    resolve('成功') 
    or
    reject('失败') 
  }
 ).then(
   res => {console.log(res)},  // 成功
 ).catch(
   err => {console.log(err)}   // 失败
 )

当然,Promise 也有缺点

  • 无法取消 Promise,一旦新建就会立即执行,无法中途取消
  • 如果不设置回调函数,无法抛出 Promise 内部错误到外部
  • 当处于 Pending 状态时,无法得知目前运行的情况,是刚开始还是快结束

事不宜迟,我们马上开始!

Promise 的状态

Promise 有以下 3 种状态:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

状态只能由 pending 向 fulfilled 或 rejected 转变,且只有在执行环境堆栈仅包含平台代码时转变一次,称为状态凝固,并保存一个参数表明结果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
this.value = value   // fulfilled状态,保存终值
this.reason = reason // rejected状态,保存据因

Promise 构造函数

promise 构造函数接受一个函数作为参数,我们称该函数参数为 executor,待 promise 执行时,会向 executor 传入两个函数参数,分别为 resolve 和 reject,它们只做 3 件事:

  • 改变 promise 状态
  • 保存 value/reason 结果
  • 执行 onFulfilled/onRejected 回调函数

其中第三条即为 then 方法中配置的回调函数,这里先不做多讨论,先看前两条,只需要两行代码即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
this.state = state
this.value = value / this.reason = reason

我们先手撸一个简单的构造函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise{
  constructor(executor) {
    this.state = PENDING  // prpmise具有初始状态
    this.value = null// 用于保存终值
    this.reason = null// 用于保存拒因

    this.onFulfilledCallbacks = [] // 成功回调队列
    this.onRejectedCallbacks = []  // 失败回调队列
    // 定义 resolve 函数
    // 这里使用箭头函数以解决 this 的指向,不了解的朋友可以先看阮大大的ES6文章
    const resolve = value => {
    // 保证状态只能改变一次
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
        }
    }
    // 同上
    const reject = reason => {
        if (this.state === PENDING) {
          this.state = REJECTED
          this.reason = reason
        }
    }
    // executor 可能会出现异常,需要捕获并调用reject函数表示执行失败
    try {
        // 传入两个函数参数
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
  }
}

看上去还不错,大概的流程已经完成了。还记得之前说过,状态的改变是处于主线程空闲时,这里使用 setTimeout 来模拟,以及 resolve/reject 还剩下第 3 件事,现在就让我们一起完善它吧:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const resolve = value => {
     // setTimeout 模拟
     // 注意 即便是判断状态是否为 pending 也应该要在主线程空闲时执行
     setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          // 若是使用 forEach 回调函数有可能不按顺序执行
          this.onFulfilledCallbacks.map(cb => cb(this.value))
        } 
     })
 }
 // reject同上

好啦,一个完整的构造函数就写完了,是不是觉得很轻松,Promise 不过如此。

接下来是重头戏 then 方法,then 接受两个函数参数,分别为 onFulfilled/onRejected,用来配置 promise 状态改变后的回调函数。

其有两个重点:

  1. 返回一个 promise2,以实现链式调用
  • 其中 promise2 的状态必须要凝固
  • 通过 resolvePromise 函数以及 onFulfilled/onRejected 的返回值来实现 promise2 的状态凝固
  1. 监听或执行对应的 onFulfilled/onRejected 回调函数
  • 若是执行则需放入 event-loop
  • 监听只需推入回调函数数组中

上述的 resolvePromise 我们先不理会,只要知道它是用来决定 promise2 的状态即可。

首先,then 需要返回一个 promise2

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
then(onFulfilled, onRejected) {
        let promise2
        return (promise2 = new MyPromise((resolve, reject) => {
            
        })
    }

其次,then 方法的目的是配置或执行对应的 onFulfilled/onRejected 回调函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
then(onFulfilled, onRejected) {
        let promise2
        return (promise2 = new MyPromise((resolve, reject) => {
            // 将回调函数配置好并推入对应的 callbacks 数组中
            this.onFulfilledCallbacks.push(value => {
                // 配置第一步:执行 callback 并保存返回值 x
                let x = onFulfilled(value);
                // 配置第二步:通过 resolvePromise 决定 promise2 状态
                resolvePromise(promise2, x, resolve, reject)
            })
            // onRejected 同上
            this.onRejectedCallbacks.push(reason => {
                let x = onRejected(reason)
                resolvePromise(promise2, x, resolve, reject)
            })
        })
    }

在这里可以大概了解 resolvePromise 是如何改变 promise2 状态的,它接受 promise2 的 resolve/reject,由于箭头函数的原因,resolve/reject 的 this 指向依旧指向 promise2,从而可以通过 resolvePromise 来改变状态。

万一 onFulfilled/onRejected 出错怎么办?我们需要将它捕获并将 promise2 的状态改为 rejected,我们将代码再做修改:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
then(onFulfilled, onRejected) {
        let promise2
        return (promise2 = new MyPromise((resolve, reject) => {
            // 将回调函数配置好并推入对应的 callbacks 数组中
            this.onFulfilledCallbacks.push(value => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            
            })
            // onRejected 同上
            this.onRejectedCallbacks.push(reason => {
                try {
                    let x = onRejected(reason)
                    resolvePromise(promise2, x, resolve, reject) 
                } catch (e) {
                    reject(e)
                }
            })
        })
      }
   }

如果调用 then 方法的是已经状态凝固的 promise 呢,也要推入 callbacks 数组吗?答案当然不是,而是直接将配置好的 onFulfilled/onRejected 扔入 event-loop 中,就不劳烦 resolve/reject 了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
then(onFulfilled, onRejected){
    // fulfilled 状态,将配置好的回调函数扔入 event-loop
    if (this.state === FULFILLED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
    // rejected 状态同上
    if (this.state === REJECTED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
    // pending 状态则交由 resolve/reject 来决定
    if (this.state === PENDING) {
      return (promise2 = new MyPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
  }
}

看上去完美了,不过还差一件小事,假如 promise 使用者不按套路出牌,传入的 onFulfilled/onRejected 不是一个函数怎么办?这里我们就直接将之作为返回值直接返回:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
then(onFulfilled, onRejected){
    let promise2
    // 确保 onFulfilled/onRejected 为函数
    // 若非函数,则转换为函数并且返回值为自身
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason
    }

    if (this.state === FULFILLED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === REJECTED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === PENDING) {
      return (promise2 = new MyPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
  }
}

大功告成!

最后只剩下一个 resolvePromise 方法,先介绍一下它的功能:根据回调函数的返回值 x 决定 promise2 的最终状态:

  • 如果 x 为 thenable 对象,即带 then 方法的对象
    • 有,因其不一定符合 promise 的标准,我们做多一些准备
    • 无,当作普通值执行
    • 使用 called 变量使得其状态改变只能发生一次
    • 监听异常
    • 递归调用 resolvePromise 以防止出现套娃
    • 如果 x 为 promise,则递归调用,直到返回值为普通值为止
    • 如果 x 为函数或对象,判断其有无 then 方法
  • x 为普通值
    • 直接返回

让我们来一步一步刨析它吧:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function resolvePromise(promise2, x, resolve, reject){
    // 先从 x 为 thenable 对象开始
    // 如果 x === promise2 需要抛出循环引用错误,否则会死循环
    if (x === promise2) {
        reject(newTypeError('循环引用'))
    }
    // 如果 x 就是 promise
    // 根据 x 的状态来决定 promise2 的状态
    if (x instanceof MyPromise) {
        // x 状态为 PENDING 时
        // 当 x 被 resolve 会调用新的 resolvePromise
        // 因为怕 resolve 保存的终值还是 promise 继续套娃
        // 所以一定要递归调用 resolvePromise 保证最终返回的一定是普通值
        // 失败直接调用 reject 即可
        if (x.state === PENDING) {
            x.then(
                y => {
                    resolvePromise(promise2, y, resolve, reject)
                },
                r => {
                    reject(r)
                }
            )
        } else {
            // x 状态凝固,直接配置即可
            // 不过这里有个疑问
            // 如果之前 resolve 保存的终值还是 promise 呢
            // 该怎样预防这一问题,后续将会讲到
            x.then(resolve, reject)
        }
    }
}

现在把应对 x 的值为 promise 的代码书写完毕,但这还不够,我们要面对的不只是 promise,而是一个 thenable 对象,所以还要继续判断:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(newTypeError('循环引用'))
  }
  if (x instanceof MyPromise) {
     // 前面的代码不再赘述
  } elseif (x && (typeof x === 'function' || typeof x === 'object')) {
      // 因为不一定是规范的 promise 对象
      // 我们需要保证状态的改变只发生一次
      // 加入一个 called 变量来加锁
      let called = false
      // 还是因为不一定是规范的 promise 对象
      // 需要保证运行时异常能够被捕获
      try {
          // 注意,前面不加 try/catch
          // 仅仅下面这一行代码也有可能会报错而无法被捕获
          let then = x.then
          // 假如 x.then 存在并为函数
          if (typeof then === 'function') {
              // 使用 call 方法保证 then 的调用对象为 x
              then.call{
                  x,
                  y => {
                      // 假如状态凝固便不再执行
                      if (called) return
                      called = true
                      // 防止出现 resolve 保存 promise 的情况
                      resolvePromise(promise2, y, resolve, reject)
                  },
                  r => {
                      // 同上
                      if (called) return
                      called = true
                      reject(r)
                  }
              } 
         } else {
            // 如果 x.then 不是函数
            // 即为普通值,直接 resolve 就好
            resolve(x)
        }
       } catch (e) {
          // 若调用一个不正规的 thenalbe 对象出错
          // 抛出异常
          // 这里要注意,这里出现错误很有可能是执行了 x.then 方法,而之前也说过,其不一定正规,可能状态已经凝固,需要多加一重保险
          if (called) return
          called = true
          reject(e)
        }
     } else {
       // 不是 thenable 对象,那就是普通值
       // 直接 resolve
       resolve(x)   
    }
}

一套行云流水的代码写下来,我们的 promise 就完成了,不过还记得之前代码里留了个疑问吗?当 x 为 promise 且状态凝固时,如果确定它保存的终值的不是 promise 呢?其实只要最开始的 resolve 函数多加一重判断即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const resolve = value => {
      if (value instanceof MyPromise) {
        return value.then(resolve, reject)
      }

      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          this.onFulfilledCallbacks.map(cb => cb(this.value))
        }
      })
    }

再次防止套娃!

好啦,也许你会问,我怎么知道这个手写的 promise 就一定是正确的呢?接下来将一步步带你验证!

首先找到一个空文件夹,在命令行输入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    npm init -y
    // 下载 promise 测试工具
    npm install promises-aplus-tests -D

新建 promise.js 文件,并将你实现的 promise 复制于此,并在下方加入一下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MyPromise.deferred = function() {
  let defer = {}
  defer.promise = new MyPromise((resolve, reject) => {
    defer.resolve = resolve
    defer.reject = reject
  });
  return defer
}

module.exports = MyPromise

再修改 package.json 文件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  "name": "promise",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "promises-aplus-tests ./promise.js"
  },
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  }
}

最后一步:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    npm run test

完成!

手写 Promise,你也可以!

最后附上完整实现代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    this.state = PENDING
    this.value = null
    this.reason = null

    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = value => {
      if (value instanceof MyPromise) {
        return value.then(resolve, reject)
      }

      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          this.onFulfilledCallbacks.map(cb => cb(this.value))
        }
      })
    }

    const reject = reason => {
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = REJECTED
          this.reason = reason
          this.onRejectedCallbacks.map(cb => cb(this.reason))
        }
      })
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onFulfilled, onRejected) {
    let promise2

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason
    }

    if (this.state === FULFILLED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === REJECTED) {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }

    if (this.state === PENDING) {
      return (promise2 = new MyPromise((resolve, reject) => {
        this.onFulfilledCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }))
    }
  }
}
function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(newTypeError('循环引用'))
  }

  if (x instanceof MyPromise) {
    if (x.state === PENDING) {
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject)
        },
        r => {
          reject(r)
        }
      )
    } else {
      x.then(resolve, reject)
    }
  } elseif (x && (typeof x === 'function' || typeof x === 'object')) {
    let called = false
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return
            called = true
            resolvePromise(promise2, y, resolve, reject)
          },
          r => {
            if (called) return
            called = true
            reject(r)
          }
        )
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

MyPromise.deferred = function() {
  let defer = {}
  defer.promise = new MyPromise((resolve, reject) => {
    defer.resolve = resolve
    defer.reject = reject
  });
  return defer
};

module.exports = MyPromise;

紧追技术前沿,深挖专业领域

扫码关注我们吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
欧拉角_欧拉角 图
来源 https://www.zhihu.com/question/47736315
全栈程序员站长
2022/09/20
7860
欧拉角_欧拉角 图
坐标转换与姿态描述
为了能够科学的反映物体的运动特性,会在特定的坐标系中进行描述,一般情况下,分析飞行器运动特性经常要用到以下几种坐标系统1、大地坐标系统;2、地心固定坐标系统;3、本地北东地坐标系统;4、机载北东地坐标系统;5、机体轴坐标系统。 其中3、4、5在我们建模、设计控制律时都是经常需要使用的坐标系,描述物体(刚体)位姿信息的6个自由度信息都是在这三个坐标系中产生的
小飞侠xp
2019/10/13
2.5K0
欧拉角和万向节死锁
有很多种方式可以描述旋转,但是使用欧拉角来描述是最容易让人理解的。这篇文章将会介绍欧拉角的基础知识、欧拉角的问题和如何去解决这些问题,当然还有欧拉角无法解决的万向节死锁问题,在最后还会介绍如何将欧拉角转换成矩阵,便于程序计算。
羽月
2022/10/08
1.4K0
欧拉角和万向节死锁
第4章-变换-4.2-特殊矩阵变换和运算
在本节中,将介绍和导出对实时图形必不可少的几个矩阵变换和运算。首先,我们介绍了欧拉变换(连同它的参数提取),这是一种描述方向的直观方式。然后我们谈到从单个矩阵中反演一组基本变换。最后,导出了一种方法,可以绕任意轴旋转实体。
charlee44
2022/01/04
3.6K0
第4章-变换-4.2-特殊矩阵变换和运算
js调用原生API--陀螺仪和加速器
介绍 W3C设备方向规范允许开发者使用陀螺仪和加速计的数据。这个功能能被用来在现代浏览器里构筑虚拟现实和增强现实的体验。但是这处理原生数据的学习曲线对开发者来说有点大。 在本文中我们要分解并解释设备方
前朝楚水
2018/04/03
4.8K0
js调用原生API--陀螺仪和加速器
四旋翼飞行器姿态控制(四轴飞行器姿态解算)
姿态航向参考系统AHRS(Attitude and Heading Reference System)
全栈程序员站长
2022/08/01
1.4K0
四旋翼飞行器姿态控制(四轴飞行器姿态解算)
关于飞机姿态角的学习分享
飞机姿态角是按欧拉概念定义的,故亦称欧拉角。飞机姿态角是由机体坐标系与地理坐标系之间的关系确定的,用航向角、俯仰角和横滚角三个欧拉角表示。
用户7053485
2020/03/19
6.6K0
姿态传感器mpu6050_六轴陀螺仪原理
陀螺仪是用高速回转体的动量矩敏感壳体相对惯性空间绕正交于自转轴的一个或二个轴的角运动检测装置。利用其他原理制成的角运动检测装置起同样功能的也称陀螺仪。 从力学的观点近似的分析陀螺的运动时,可以把它看成是一个刚体,刚体上有一个万向支点,而陀螺可以绕着这个支点作三个自由度的转动,所以陀螺的运动是属于刚体绕一个定点的转动运动。更确切地说,一个绕对称铀高速旋转的飞轮转子叫陀螺。将陀螺安装在框架装置上,使陀螺的自转轴有角转动的自由度,这种装置的总体叫做陀螺仪。 陀螺仪的原理就是,一个旋转物体的旋转轴所指的方向在不受外力影响时,是不会改变的。人们根据这个道理,用它来保持方向,制造出来的东西就叫陀螺仪。我们骑自行车其实也是利用了这个原理。轮子转得越快越不容易倒,因为车轴有一股保持水平的力量。陀螺仪在工作时要给它一个力,使它快速旋转起来,一般能达到每分钟几十万转,可以工作很长时间。然后用多种方法读取轴所指示的方向,并自动将数据信号传给控制系统。
全栈程序员站长
2022/11/19
2K0
姿态传感器mpu6050_六轴陀螺仪原理
四旋翼飞行器1——结构和控制原理
四轴飞行器是一个在空间具有6个活动自由度(分别沿3个坐标轴作平移和旋转动作),但是只有4个控制自由度(四个电机的转速)的系统,因此被称为欠驱动系统(只有当控制自由度等于活动自由度的时候才是完整驱动系统)
全栈程序员站长
2022/07/23
1.8K0
四旋翼飞行器1——结构和控制原理
四旋翼无人飞行器自主飞行控制原理
形式如图所示,电机1和电机3逆时针旋转的同时,电机2和电机4顺时针旋转,因此当飞行器平衡飞行时,陀螺效应和空气动力扭矩效应均被抵消。 与传统的直升机相比,四旋翼飞行器有下列优势:各个旋翼对机身所施加的反扭矩与旋翼的旋转方向相反,因此当电机1和电机3逆时针旋转的同时,电机2和电机4顺时针旋转,可以平衡旋翼对机身的反扭矩。四旋翼飞行器在空间共有6个自由度(分别沿3个坐标轴作平移和旋转动作),这6个自由度的控制都可以通过调节不同电机的转速来实现。 其基本运动状态分别是: (1)垂直运动;(2)俯仰运动; (3
机器人网
2018/04/12
2.2K0
四旋翼无人飞行器自主飞行控制原理
三维空间的刚体运动
一个刚体在三维空间中的运动如何描述? 我们知道是由旋转加平移组成的,平移很简单,但是旋转有点麻烦。 三维空间的刚体运动的描述方式:旋转矩阵、变换矩阵、四元数、欧拉角。 刚体,不光有位置,而且还有姿态。相机可以看成是三维空间的一个刚体,位置指的就是相机在空间处于哪个地方?而姿态指的是相机的朝向(例如:相机位于(0, 0,0)点处,朝向正东方)但是这样去描述比较繁琐。
Albert_xiong
2021/06/21
1.1K0
三维空间的刚体运动
树莓派基础实验31:MPU6050陀螺仪加速度传感器实验
   MPU6050是世界上第一款也是唯一一款专为智能手机、平板电脑和可穿戴传感器的低功耗、低成本和高性能要求而设计的6轴运动跟踪设备。    它集成了3轴MEMS陀螺仪,3轴MEMS加速度计,以及一个可扩展的数字运动处理器 DMP( DigitalMotion Processor),可用I2C接口连接一个第三方的数字传感器,比如磁力计。扩展之后就可以通过其 I2C或SPI接口输出一个9轴的信号( SPI接口仅在MPU-6000可用)。 MPU-60X0也可以通过其I2C接口连接非惯性的数字传感器,比如压力传感器。
张国平
2020/09/27
5.9K0
加速计和陀螺仪
iPhone在静止时会受到地球引力,以屏幕中心为坐标原点,建立一个三维坐标系(如右图),此时iPhone收到的地球引力会分布到三个轴上。 iOS开发者可以通过CoreMotion框架获取分布到三个轴的值。如果iPhone是如图放置,则分布情况为x=0,y=-1.0,z=0。 在CoreMotion中地球引力(重力)的表示为1.0。
落影
2019/01/28
2.1K0
加速计和陀螺仪
从零开始学习自动驾驶系统(八)-基础知识之车辆姿态表达
辆位置和姿态是自动驾驶中的一个基础问题,只有解决了车辆的位置和姿态,才能将自动驾驶的各个模块关联起来。车辆的位置和姿态一般由自动驾驶的定位模块输出。
YoungTimes
2022/04/28
2.9K0
从零开始学习自动驾驶系统(八)-基础知识之车辆姿态表达
Unity中陀螺仪控制
这里就直接上代码,代码带有注释,后续引用在更新! private const float lowPassFilterFactor = 0.2f; Gyroscope go; bool gyinfo; protected void Start() { gyinfo = SystemInfo.supportsGyroscope; go = Input.gyro; //设置设备陀螺仪的开启/关闭状态,使用陀螺仪功能必须设置
bering
2019/12/03
2.3K0
无人机中的IMU单元(MEMS 三轴加速计、三轴陀螺仪、三轴磁力计)
三轴加速度计是一种惯性传感器,能够测量物体的比力,即去掉重力后的整体加速度或者单位质量上作用的非引力。当加速度计保持静止时,加速度计能够感知重力加速度,而整体加速度为零。在自由落体运动中,整体加速度就是重力加速度,但加速度计内部处于失重状态,而此时三轴加速度计输出为零。
3D视觉工坊
2020/12/11
3.1K0
无人机中的IMU单元(MEMS 三轴加速计、三轴陀螺仪、三轴磁力计)
陀螺仪相关测试电路
陀螺仪是无人机惯导系统最基本的组成元件之一,通过对陀螺仪输出的角速度进行积分,能够获得无人机的姿态角信息;在兴趣爱好的驱动下,近来购买了MPU-6050相关模块,通过串口把测试结果传输到电脑端,实现了位姿信号的采集,具体如下图所示:
联远智维
2022/01/20
9440
陀螺仪相关测试电路
四轴飞行器姿态控制算法
姿态解算 姿态解算(attitude algorithm),是指把陀螺仪,加速度计, 罗盘等的数据融合在一起,得出飞行器的空中姿态,飞行器从陀螺仪器的三轴角速度通过四元数法得到俯仰,航偏,滚转角,这是
机器人网
2018/04/25
2.1K0
四轴飞行器姿态控制算法
《游戏引擎架构》阅读笔记 第一部分第4章
本系列博客为《游戏引擎架构》一书的阅读笔记,旨在精炼相关内容知识点,记录笔记,以及根据目前(2022年)的行业技术制作相关补充总结。 本书籍无硬性阅读门槛,但推荐拥有一定线性代数,高等数学以及编程基础,最好为制作过完整的小型游戏demo再来阅读。 本系列博客会记录知识点在书中出现的具体位置。并约定(Pa b),其中a为书籍中的页数,b为从上往下数的段落号,如有lastb字样则为从下往上数第b段。 本系列博客会约定用【】来区别本人所书写的与书中观点不一致或者未提及的观点,该部分观点受限于个人以及当前时代的视角
[Sugar]
2022/10/28
3760
《游戏引擎架构》阅读笔记 第一部分第4章
【GAMES101】三维变换
games101的第四节课讲了三维变换和观察变换,我们这里先记录一下三维变换的知识,后面再讲观察变换
叶茂林
2023/12/08
2010
【GAMES101】三维变换
推荐阅读
相关推荐
欧拉角_欧拉角 图
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验