这个未定义:可能异步到生成器问题

this undefined: possible async to generator issue

我从我用于 firebase 休息事务的库中获取 'client' undefined,我很确定我以前使用过它没有问题。当我查看库时(intellij websphere 调试器将我带到一个打字稿文件,必须是地图感知的),我看到 this.client...,在我调用的 class 方法中,所以这个应该肯定被定义。

然后我抓取了一个不同的库,它恰好是一个文件,所以我可以将它直接放在我的 src/ 文件夹中,(当周围有其他破损的轮子时,为什么要修复一个平面,对吧?)和.. 同样的事情(只是这次是 this.url 因为图书馆不同)。

基本结构 确实 有效,因为:(将注释代码与内联 Promise 进行比较)

class FirebaseDb {
  constructor() {}

  async tx(command, path, payload, queryParams) {
    const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]]
    /*const unary = (command === 'get' || commmand === 'delete')
    const operation = await (unary?
      dbcmd(path, queryParams): 
      dbcmd(path, payload, queryParams))*/
     const res = await new Promise((r,e)=>setTimeout(
        ()=>{
          console.log('inner this: ' + this);
          r({foo:'bar'})
        }, 1000))
      const operation = {ok: true, body: res}

    if(!operation.ok)
      throw new Error(`[FirebaseDb] ${FirebaseDb.fbcrest[command]} - error with TX. (status ${operation.status}: ${operation.statusText})`)

    return operation.body
  }

这个承诺很好用。我点击 api url 并在一秒钟后得到 {"foo":"bar"}。控制台不显示 undefined this,它报告 inner this: [object Object].

所以这是完整的两个文件(该片段来自firebaseDb.js,因为它只是一个演示,所以不会在下面显示):

firebaseDb.js

import restFirebase from './restful-firebase'
import config from 'config'

/* FirebaseDb is a specific Model Db Firebase object. It receives REST calls
   and converts them to firebase REST calls, executes them on a firebase 
   connection, returning the result or erroring out if there is an error. this
   could be swapped out with a similar one for MongoDB, etc */
class FirebaseDb {
  constructor() {}

  async tx(command, path, payload, queryParams) {
    const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]]
    const unary = (command === 'get' || commmand === 'delete')
    const operation = await (unary?
      dbcmd(path, queryParams): 
      dbcmd(path, payload, queryParams))

    if(!operation.ok)
      throw new Error(`[FirebaseDb] ${FirebaseDb.fbcrest[command]} - error with TX. (status ${operation.status}: ${operation.statusText})`)

    return operation.body
  }

}
FirebaseDb.fbcrest = {
  get: 'get',
  put: 'set',
  patch: 'update',
  post: 'push',
  delete: 'remove'
}
FirebaseDb.db = restFirebase.factory(config.firebase.project)
FirebaseDb.ref = FirebaseDb.db({
  paths: '/',
  auth: config.firebase.auth
})

export default FirebaseDb

restful-firebase.js

(修改了 npm i rest-firebase,在 tx 调用中接受路径,不使用 strict 进行调试):

import request from 'request'

const TIMEOUT = 5000;
const baseRequest = request.defaults({timeout: TIMEOUT, json: true});
const VALID_ID = /^[-0-9a-zA-Z]{2,}$/;
const VALID_URL = /^https?:\/\/[\da-z\.-]+(\:\d+)?\/?$/;
const ERR_INVALID_ID = 'Invalid Firebase id.';
const ERR_NO_SECRET = 'A Firebase secret is required for this operation.';

class ResponseError extends Error {

  constructor(opts, resp, body) {
    super(resp.statusMessage);
    this.name = 'ResponseError';

    this.url = opts.url;
    this.method = opts.method;
    this.status = resp.statusCode;
    this.authDebug = resp.headers['x-firebase-auth-debug'];
    this.body = body;
  }
}

class Request {

  constructor(opts) {
    this.rootPath = trimPath(opts.rootPath);
    this.url = opts.url;
    this.auth = opts.auth;
    this.$logger = opts.logger || console;
  }

  toString() {
    return Request.fixUrl(this.url);
  }

  static fixUrl(url) {
    return url.endsWith('.json') ? url : `${url}.json`;
  }

  process(url, method, qs, payload) {
    return new Promise((resolve, reject) => {
      const opts = {
        url: Request.fixUrl(url),
        method: method,
        qs: Object.assign({auth: this.auth}, qs)
      };

      if (payload !== undefined) {
        opts.body = payload;
      }

      baseRequest(opts, (err, resp, body) => {
        if (err) {
          reject(err);
          return;
        }

        const debugMessage = resp.headers['x-firebase-auth-debug'];

        if (debugMessage) {
          this.$logger.warn(debugMessage);
        }

        if (resp.statusCode >= 300) {
          reject(new ResponseError(opts, resp, body));
          return;
        }

        resolve(body);
      });
    });
  }

  rules(rules) {
    if (!this.auth) {
      return Promise.reject(new Error(ERR_NO_SECRET));
    }

    const opts = {
      'method': 'GET',
      'url': `${this.rootPath}/.settings/rules.json`,
      'qs': {auth: this.auth}
    };

    return new Promise((resolve, reject) => {
      if (rules) {
        opts.method = 'PUT';
        opts.body = rules;
        opts.json = typeof(rules) === 'object';
      }

      request(opts, (err, resp, body) => {
        if (err) {
          reject(err);
          return;
        }

        if (resp.statusCode >= 300) {
          reject(new ResponseError(opts, resp, body));
          return;
        }

        resolve(body);
      });
    });
  }

  get(path, qs) {
    let url = this.url
    if(path)
      url += '/' + path
    return this.process(url, 'GET', qs);
  }

  set(path, payload, qs) {
    let url = this.url
    if(path)
      url += '/' + path
    return this.process(url, 'PUT', qs, payload);
  }

  update(path, payload, qs) {
    let url = this.url
    if(path)
      url += '/' + path

    if (url.endsWith('/.json')) {
// no-op
    } else if (url.endsWith('.json')) {
      url = `${url.slice(0, -5)}/.json`;
    } else if (url.endsWith('/')) {
      url = `${url}.json`;
    } else {
      url = `${url}/.json`;
    }

    return this.process(url, 'PATCH', qs, payload);
  }

  push(path, patch, qs) {
    let url = this.url
    if(path)
      url += '/' + path
    return this.process(url, 'POST', qs, patch);
  }

  remove(path, qs) {
    let url = this.url
    if(path)
      url += '/' + path
    return this.process(url, 'DELETE', qs);
  }
}

function trimPath(path) {
  return path.replace(/\/+$/, '');
}

/**
 * Create a firebase rest client factory.
 *
 * The clients will be bound to a firebase ID. You then can use relative path
 * to create references to entities in your Firebase DB.
 *
 * Usage:
 *
 *    const restFirebase = require('rest-firebase');
 *    const firebase = restFirebase.factory('some-id');
 *    const ref = firebase({paths: 'some/path', auth: 'some-oauth-token'});
 *
 *    // you can pass parameters
 *    // (see https://www.firebase.com/docs/rest/api/#section-query-parameters)
 *    ref.get({shallow: true}).then(value => {
 *        // ...
 *    });
 *
 * @param  {string}   target Firebase ID or URL
 * @return {function}
 *
 */
function restFirebaseFactory(target) {
  let rootPath;

  if (VALID_URL.test(target)) {
    rootPath = trimPath(target);
  } else if (VALID_ID.test(target)) {
    rootPath = `https://${target}.firebaseio.com`;
  } else {
    throw new Error(ERR_INVALID_ID);
  }

  function restFirebase(opts) {
    const relPaths = opts && opts.paths || '';
    const url = [rootPath].concat(relPaths).join('/');

    return new Request(
      Object.assign({}, opts, {rootPath, url})
    );
  }

  return restFirebase;
}

exports.Request = Request;
exports.factory = restFirebaseFactory;

可能涉及的一些"magic":

我的.babelrc

{
  "presets": ["latest"]
}

我的 gulpfile:

const gulp = require('gulp');
const babel = require('gulp-babel');
const clean = require('gulp-rimraf');

const dest = 'dist'

gulp.task('src', ['clean'], () => {
    return gulp.src(['server.js', 'src/**/*.js'])
        .pipe(babel({
            plugins: ['syntax-async-functions','transform-async-to-generator', 'transform-runtime']
        }))
        .pipe(gulp.dest(dest));
});

gulp.task('node_modules', ['clean', 'src'], () => {
    return gulp.src(['node_modules/vue/dist/vue.js', 'node_modules/vue-router/dist/vue-router.js', 'node_modules/vuex/dist/vuex.js'])
        .pipe(gulp.dest(dest+'/node_modules'));
});

gulp.task('clean', () => {
    gulp.src(dest+'/*', {read:false})
        .pipe(clean())         
})

gulp.task('default', ['node_modules', 'src'])

这样做是为了 restful-firebase.js:

'use strict';

var _typeof2 = require('babel-runtime/helpers/typeof');

var _typeof3 = _interopRequireDefault(_typeof2);

var _assign = require('babel-runtime/core-js/object/assign');

var _assign2 = _interopRequireDefault(_assign);

var _promise = require('babel-runtime/core-js/promise');

var _promise2 = _interopRequireDefault(_promise);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

var _request = require('request');

var _request2 = _interopRequireDefault(_request);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var TIMEOUT = 5000;
var baseRequest = _request2.default.defaults({ timeout: TIMEOUT, json: true });
var VALID_ID = /^[-0-9a-zA-Z]{2,}$/;
var VALID_URL = /^https?:\/\/[\da-z\.-]+(\:\d+)?\/?$/;
var ERR_INVALID_ID = 'Invalid Firebase id.';
var ERR_NO_SECRET = 'A Firebase secret is required for this operation.';

var ResponseError = function (_Error) {
  (0, _inherits3.default)(ResponseError, _Error);

  function ResponseError(opts, resp, body) {
    (0, _classCallCheck3.default)(this, ResponseError);

    var _this = (0, _possibleConstructorReturn3.default)(this, (ResponseError.__proto__ || (0, _getPrototypeOf2.default)(ResponseError)).call(this, resp.statusMessage));

    _this.name = 'ResponseError';

    _this.url = opts.url;
    _this.method = opts.method;
    _this.status = resp.statusCode;
    _this.authDebug = resp.headers['x-firebase-auth-debug'];
    _this.body = body;
    return _this;
  }

  return ResponseError;
}(Error);

var Request = function () {
  function Request(opts) {
    (0, _classCallCheck3.default)(this, Request);

    this.rootPath = trimPath(opts.rootPath);
    this.url = opts.url;
    this.auth = opts.auth;
    this.$logger = opts.logger || console;
  }

  (0, _createClass3.default)(Request, [{
    key: 'toString',
    value: function toString() {
      return Request.fixUrl(this.url);
    }
  }, {
    key: 'process',
    value: function process(url, method, qs, payload) {
      var _this2 = this;

      return new _promise2.default(function (resolve, reject) {
        var opts = {
          url: Request.fixUrl(url),
          method: method,
          qs: (0, _assign2.default)({ auth: _this2.auth }, qs)
        };

        if (payload !== undefined) {
          opts.body = payload;
        }

        baseRequest(opts, function (err, resp, body) {
          if (err) {
            reject(err);
            return;
          }

          var debugMessage = resp.headers['x-firebase-auth-debug'];

          if (debugMessage) {
            _this2.$logger.warn(debugMessage);
          }

          if (resp.statusCode >= 300) {
            reject(new ResponseError(opts, resp, body));
            return;
          }

          resolve(body);
        });
      });
    }
  }, {
    key: 'rules',
    value: function rules(_rules) {
      if (!this.auth) {
        return _promise2.default.reject(new Error(ERR_NO_SECRET));
      }

      var opts = {
        'method': 'GET',
        'url': this.rootPath + '/.settings/rules.json',
        'qs': { auth: this.auth }
      };

      return new _promise2.default(function (resolve, reject) {
        if (_rules) {
          opts.method = 'PUT';
          opts.body = _rules;
          opts.json = (typeof _rules === 'undefined' ? 'undefined' : (0, _typeof3.default)(_rules)) === 'object';
        }

        (0, _request2.default)(opts, function (err, resp, body) {
          if (err) {
            reject(err);
            return;
          }

          if (resp.statusCode >= 300) {
            reject(new ResponseError(opts, resp, body));
            return;
          }

          resolve(body);
        });
      });
    }
  }, {
    key: 'get',
    value: function get(path, qs) {
      var url = this.url;
      if (path) url += '/' + path;
      return this.process(url, 'GET', qs);
    }
  }, {
    key: 'set',
    value: function set(path, payload, qs) {
      var url = this.url;
      if (path) url += '/' + path;
      return this.process(url, 'PUT', qs, payload);
    }
  }, {
    key: 'update',
    value: function update(path, payload, qs) {
      var url = this.url;
      if (path) url += '/' + path;

      if (url.endsWith('/.json')) {
        // no-op
      } else if (url.endsWith('.json')) {
        url = url.slice(0, -5) + '/.json';
      } else if (url.endsWith('/')) {
        url = url + '.json';
      } else {
        url = url + '/.json';
      }

      return this.process(url, 'PATCH', qs, payload);
    }
  }, {
    key: 'push',
    value: function push(path, patch, qs) {
      var url = this.url;
      if (path) url += '/' + path;
      return this.process(url, 'POST', qs, patch);
    }
  }, {
    key: 'remove',
    value: function remove(path, qs) {
      var url = this.url;
      if (path) url += '/' + path;
      return this.process(url, 'DELETE', qs);
    }
  }], [{
    key: 'fixUrl',
    value: function fixUrl(url) {
      return url.endsWith('.json') ? url : url + '.json';
    }
  }]);
  return Request;
}();

function trimPath(path) {
  return path.replace(/\/+$/, '');
}

/**
 * Create a firebase rest client factory.
 *
 * The clients will be bound to a firebase ID. You then can use relative path
 * to create references to entities in your Firebase DB.
 *
 * Usage:
 *
 *    const restFirebase = require('rest-firebase');
 *    const firebase = restFirebase.factory('some-id');
 *    const ref = firebase({paths: 'some/path', auth: 'some-oauth-token'});
 *
 *    // you can pass parameters
 *    // (see https://www.firebase.com/docs/rest/api/#section-query-parameters)
 *    ref.get({shallow: true}).then(value => {
 *        // ...
 *    });
 *
 * @param  {string}   target Firebase ID or URL
 * @return {function}
 *
 */
function restFirebaseFactory(target) {
  var rootPath = void 0;

  if (VALID_URL.test(target)) {
    rootPath = trimPath(target);
  } else if (VALID_ID.test(target)) {
    rootPath = 'https://' + target + '.firebaseio.com';
  } else {
    throw new Error(ERR_INVALID_ID);
  }

  function restFirebase(opts) {
    var relPaths = opts && opts.paths || '';
    var url = [rootPath].concat(relPaths).join('/');

    return new Request((0, _assign2.default)({}, opts, { rootPath: rootPath, url: url }));
  }

  return restFirebase;
}

exports.Request = Request;
exports.factory = restFirebaseFactory;

特别是 this 未定义的代码部分,抛出错误的是第 3 行:

    key: 'get',
    value: function get(path, qs) {
      var url = this.url;
      if (path) url += '/' + path;
      return this.process(url, 'GET', qs);
    }

通常假设在这种情况下该方法被调用为回调是安全的,这可以通过使用箭头函数或 bind 来解决。如果对象方法未绑定,则不能从对象中分离出来。

考虑到

const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]]

是名胜,应该是

const dbcmd = FirebaseDb.ref[FirebaseDb.fbcrest[command]].bind(FirebaseDb.ref)

const dbcmd = (...args) => FirebaseDb.ref[FirebaseDb.fbcrest[command]](...args)