代码之家  ›  专栏  ›  技术社区  ›  DDave

避免javascript中循环的多个返回-异步/等待以解决回调金字塔或回调地狱,

  •  1
  • DDave  · 技术社区  · 9 年前

    我有这段代码,有很多返回块,例如SignUp()

      const connectors = {
          Auth: {
            signUp(args) {
              return new Promise((resolve, reject) => {
                // Validate the data
                if (!args.email) {
                  return reject({
                    code: 'email.empty',
                    message: 'Email is empty.'
                  });
                } else if (!isEmail(args.email)) {
                  return reject({
                    code: 'email.invalid',
                    message: 'You have to provide a valid email.'
                  });
                }
    
                if (!args.password) {
                  return reject({
                    code: 'password.empty',
                    message: 'You have to provide a password.'
                  });
                }
    
                return encryptPassword(args.password, (err, hash) => {
                  if (err) {
                    return reject(new Error('The password could not be hashed.'));
                  }
    
                  return User.create(Object.assign(args, { password: hash }))
                    .then((user) => {
                      resolve(createToken({ id: user._id, email: user.email }));
                    })
                    .catch((err2) => {
                      if (err2.code === 11000) {
                        return reject({
                          code: 'user.exists',
                          message: 'There is already a user with this email.'
                        });
                      }
    
                      return reject(err2);
                    });
                });
              });
            },
        };
    
        module.exports = connectors;
    

    然后是另一个调用此代码的代码:

     const connectors = require('./connectors');
    
       CallsignUp(root, args) {
          const errors = [];
    
          return connectors.Auth.signUp(args)
            .then(token => ({
              token,
              errors
            }))
            .catch((err) => {
              if (err.code && err.message) {
                errors.push({
                  key: err.code,
                  value: err.message
                });
                return { token: null, errors };
              }
    
              throw new Error(err);
            });
        } 
    

    在ES6、ES7或ES2017中如何避免这种情况?

      return()
       .then()
         return()
           .then
    

    循环返回:

    return()
       return()
           return()
    

    从PHP提交这段代码看起来很疯狂,因为返回函数就是返回函数,我不清楚javascript中这种类型的块返回代码的名称是什么?调用再次返回更多代码的函数?

    更新:

    https://github.com/jferrettiboke/react-auth-app-example

    我想通过例子来理解:

        return encryptPassword(args.password, (err, hash) => {
          if (err) {
            return reject(new Error('The password could not be hashed.'));
          }
    
          return User.create(Object.assign(args, { password: hash }))
            .then((user) => {
            .catch((err2) => {
    
              return reject(err2);
    

    const jwt = require('jsonwebtoken');
    const bcrypt = require('bcrypt-nodejs');
    const config = require('../config');
    exports.encryptPassword = (password, callback) => {
      // Generate a salt then run callback
      bcrypt.genSalt(10, (err, salt) => {
        if (err) { return callback(err); }
    
        // Hash (encrypt) our password using the salt
        return bcrypt.hash(password, salt, null, (err2, hash) => {
          if (err2) { return callback(err2); }
          return callback(null, hash);
        });
      });
    };
    

    有3个返回调用函数和返回值和函数?OOP从来不是这样的,如何使用@dashmud提出的异步/等待建议,我不想学习回调之类的老东西

    3 回复  |  直到 9 年前
        1
  •  4
  •   jfriend00    9 年前

    这段代码需要以多种方式重构。首先,你真的,真的不想在同一个逻辑流中混合承诺和普通异步回调。它把事情搞得一团糟,破坏了承诺的许多优点。然后,你有一个反模式在里面使用承诺 new Promise() . 然后,您需要更多的嵌套(您可以改为链)。

    以下是我的建议:

    function encryptPasswordPromise(pwd) {
        return new Promise((resolve, reject) => {
            encryptPassword(pwd, (err, hash) => {
                err ? reject(new Error("The password could not be hashed.")) : resolve(hash);
            });
        });
    }
    
    const connectors = {
        Auth: {
            signUp(args) {
                // Validate the data
                let err;
                if (!args.email) {
                    err = {code: 'email.empty', message: 'Email is empty.'};
                } else if (!isEmail(args.email)) {
                    err = {code: 'email.invalid', message: 'You have to provide a valid email.'};
                } else if (!args.password) {
                    err = {code: 'password.empty', message: 'You have to provide a password.'};
                }
                if (err) {
                    return Promise.reject(err);
                } else {
                    return encryptPasswordPromise(args.password).then(hash => {
                        args.password = hash;
                        return User.create(args);
                    }).then((user) => {
                        return createToken({id: user._id, email: user.email});
                    }).catch(err2 => {
                        if (err2.code === 11000) {
                            throw new Error({code: 'user.exists', message: 'There is already a user with this email.'});
                        } else {
                            throw err2;
                        }
                    });
                }
            }
        }
    };
    

    1. 最初收集所有错误并返回 Promise.reject(err) 所有这些初始错误都在一个地方。
    2. encryptPassword() 因此,您不会将常规回调与承诺逻辑流混合在一起,然后您可以正确地返回和传播错误。
    3. 删除整个代码的包装 return new Promise()
    4. 撤消不必要的嵌套,而只是链。
    5. 去除 Object.assign() 因为似乎没有原因。

    return encryptPassword(args.password, (err, hash) => {
      if (err) {
        return reject(new Error('The password could not be hashed.'));
      }
    
      return User.create(Object.assign(args, { password: hash }))
        .then((user) => {
        .catch((err2) => {
    
          return reject(err2);
    
    1. 此代码调用 encryptPassword() .
    2. undefined return 正在做的是控制程序流(退出包含函数),但由于返回后没有代码处于同一级别,因此没有必要这样做。
    3. encryptPassword() . 该回调是异步的,这意味着它会在一段时间后调用。
    4. err 和 hash . 在节点中。js异步调用约定,如果第一个参数是truthy(例如,包含一些truthy值),则表示错误。如果是假的(通常是 null
    5. 如果出现错误,则拒绝父承诺并退出回调。同样,没有来自的返回值 reject 所以 回来
    6. User.create()
    7. 返回承诺,以便使用 .then()
    8. 一段时间后,当异步 完成后,它将解决其承诺,这将导致 .然后() .然后() 不会调用回调,而是调用 .catch() 回调时,父承诺被拒绝。这是一种承诺反模式(在另一个承诺中解析父承诺),因为在正确的错误处理中很容易出错。我的回答说明了如何避免这种情况。
        2
  •  3
  •   Community Mohan Dere    6 年前

    @jfriend00's answer ,这是一种使用 ES2017 async / await callback pyramid 或 callback hell ,但您更愿意将其称为:

    const encryptPasswordPromise = require('util').promisify(encryptPassword)
    
    const connectors = {
      Auth: {
        async signUp (args) {
          const { email, password } = args
          // Validate the data
          let err
    
          if (!email) {
            err = { code: 'email.empty', message: 'Email is empty.' }
          } else if (!isEmail(email)) {
            err = { code: 'email.invalid', message: 'You have to provide a valid email.' }
          } else if (!password) {
            err = { code: 'password.empty', message: 'You have to provide a password.' }
          }
    
          if (err) {
            throw err
          }
    
          let hash
    
          try {
            hash = await encryptPasswordPromise(password)
          } catch (err) {
            throw new Error('The password could not be hashed.')
          }
    
          const { _id: id, email } = await User.create(Object.assign(args, { password: hash }))
    
          try {
            return createToken({ id, email })
          } catch (err) {
            if (err.code === 11000) {
              throw { code: 'user.exists', message: 'There is already a user with this email.' }
            } else {
              throw err
            }
          }
        }
      }
    }
    
    module.exports = connectors
    

    而不是手写我自己的承诺 encryptPassword() util.promisify() .

    总的来说,还有一些可以改进的地方,比如移动 signUp() 一个单独函数的参数,但这些都与平坦化“回调地狱”反模式无关,该模式产生了基于承诺的控制流和异步/等待语法。

        3
  •  -1
  •   Noel Llevares    9 年前

    第一件事。代码中没有循环。

    如果您来自PHP,那么我猜Javascript的异步执行模型对您来说是陌生的。我建议学习更多关于Javascript的知识。

    按以下顺序学习以下内容:

    Async/await最具可读性,但如果您了解Async/await是如何形成的,那就更好了,因为如果您不了解自己在做什么,很容易使用错误的方式。

    推荐文章