简体   繁体   中英

What is wrong with my Javascript promise implementation?

I'm trying to build my own Promise to improve my understanding of Promise in javascript. I'm currently stuck at the .then method and I'd like to ask: In the documentation for .then here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then , it says that the then method returns a Promise. I'm having difficulties writing this because it seem like this.result is undefined . Why is that and how do I fix this?

Here's my code:

class LitePromise {
  constructor(fn) {
    if (typeof fn !== "function") {
      throw new TypeError("Promises have to be functions");
    }
    this.fn = fn;
    this.state = PENDING;
    this.result = null;
    const resolveCallback = this.resolve.bind(this);
    const rejectCallback = this.reject.bind(this);
    try {
      fn(resolveCallback, rejectCallback);
    } catch (err) {
      this.reject(err);
    }
  }

  resolve(arg) {
    console.log(arg, "arg");
    this.result = arg;
    this.state = RESOLVED;
  }

  reject() {
    this.state = REJECTED;
  }

  // make an instance of this promise
  then(callback) {
    const tinyPromise = new LitePromise(this.fn);
    console.log(callback, "callback");
    try {
      return tinyPromise.resolve(callback(this.result));
    } catch {
      return tinyPromise.reject(callback(this.result));
    }
  }
}

console.log("------------------");
const yaypromise = new LitePromise((resolve, reject) => {
  console.log("1. running");
  setTimeout(() => {
    console.log("2. resolving");
    resolve("yay");
  }, 100);
}).then((result) => {
  console.log("3. evaluating");
  if (result !== "yay") {
    console.log("Is not working yet");
    return;
  }
  console.log("SUCCESS!", result);
});

I think the core issue here is the following. then() typically gets processed in 2 different ways:

  1. The promise is pending. Store the callback(s) passed to then() and call those callbacks when the promise gets resolved later .
  2. The promise has a result (it resolved or rejected), in which case we'll call the callback passed to then as soon as possible.

You're never handling case 1, so if the promise resolves after then() gets called, it won't work.

In addition, the sub promise you return from then() should itself resolve one the result of the callback passed to then() completes.

If this sounds super confusing, it's because it's hard =) I would recommend to try and get your logic right first for just dealing with the callbacks in .then() , and don't return anything from .then() yet .

I also made my own promise for the same reason. It's pretty minimal, maybe it's helpful:

https://github.com/evert/promise-demo/blob/master/src/my-promise.js

Here is a sample program we expect to work with our promise ; note the lowercase p -

const delay = ms =>
  new promise(r => setTimeout(r, ms))
  
const roll = n =>
{ console.log("rolling...")
  return delay(1000)
    .then(_ => Math.ceil(Math.random() * n))
    .then(x => { console.log(x); return x })
}

const main = _ =>
  roll(20).then(x =>
    roll(20).then(y =>
      roll(20).then(z =>
        { console.log("three rolls:", x, y, z)
          return [x,y,z]
        }
      )
    )
  )

main()
  .then(([x,y,z]) => x + y + z)
  .then(sum => console.log("sum:", sum))
  .catch(console.error)
rolling...
12
rolling...
18
rolling...
15
three rolls: 12 18 15
sum: 45

As others commented there is a lot to fix in your code. But don't feel bad as Promise is not particularly easy to implement. Here's my first sketch of promise . Note value , resolved , rejected are parameters of the constructor but they should be made private. This can be done using a variety of techniques, but for sake of simplicity I left it this way for now. The caller is only intended to pass the first argument, exec -

class promise
{ constructor(exec, value, resolved = false, rejected = false)
  { this.value = value
    this.resolved = resolved
    this.rejected = rejected
    this.callback = []
    if (this.resolved || this.rejected) return
    exec(x => this.resolve(x), e => this.reject(e))
  }
  
  resolve(value)
  { if (this.resolved || this.rejected) return
    let p = promise.resolve(value)
    for (const [ifResolved, ifRejected] of this.callback)
      p = p.then(ifResolved, ifRejected)
    Object.assign(this, p)
  }
  
  reject(value)
  { if (this.resolved || this.rejected) return
    let p = promise.reject(value)
    for (const [ifResolved, ifRejected] of this.callback)
      p = p.then(ifResolved, ifRejected)
    Object.assign(this, p)
  }
  
  then(ifResolved, ifRejected = promise.reject)
  { if (this.resolved)
    { try
      { return promise.resolve(ifResolved(this.value)) }
      catch (err)
      { return promise.reject(err) }
    }
    else if (this.rejected)
    { try
      { return promise.resolve(ifRejected(this.value)) }
      catch (err)
      { return promise.reject(err) }
    }
    else
    { this.callback.push([ifResolved, ifRejected])
      return this
    }
  }
  
  catch(ifRejected)
  { return this.then(value => value, ifRejected) }
  
  static resolve(value)
  { return (value instanceof promise)
      ? value
      : new promise(_ => {}, value, true, false)
  }
  
  static reject(value)
  { return (value instanceof promise)
      ? value
      : new promise(_ => {}, value, false, true)
  }
}

Like Promise, only p.then , p.catch should be available on promise objects. We should prevent the user from calling p.resolve or p.reject directly, but for now it makes it easy to see how things work. The class functions promise.resolve and promise.reject are analogous to Promise.resolve and Promise.reject .

Expand the snippet below to verify the result in your own browser -

 class promise { constructor(exec, value, resolved = false, rejected = false) { this.value = value this.resolved = resolved this.rejected = rejected this.callback = [] if (this.resolved || this.rejected) return exec(x => this.resolve(x), e => this.reject(e)) } resolve(value) { if (this.resolved || this.rejected) return let p = promise.resolve(value) for (const [ifResolved, ifRejected] of this.callback) p = p.then(ifResolved, ifRejected) Object.assign(this, p) } reject(value) { if (this.resolved || this.rejected) return let p = promise.reject(value) for (const [ifResolved, ifRejected] of this.callback) p = p.then(ifResolved, ifRejected) Object.assign(this, p) } then(ifResolved, ifRejected = promise.reject) { if (this.resolved) { try { return promise.resolve(ifResolved(this.value)) } catch (err) { return promise.reject(err) } } else if (this.rejected) { try { return promise.resolve(ifRejected(this.value)) } catch (err) { return promise.reject(err) } } else { this.callback.push([ifResolved, ifRejected]) return this } } catch(ifRejected) { return this.then(value => value, ifRejected) } static resolve(value) { return (value instanceof promise)? value: new promise(_ => {}, value, true, false) } static reject(value) { return (value instanceof promise)? value: new promise(_ => {}, value, false, true) } } const delay = ms => new promise(r => setTimeout(r, ms)) const roll = n => { console.log("rolling...") return delay(1000).then(_ => Math.ceil(Math.random() * n)).then(x => { console.log(x); return x }) } const main = _ => roll(20).then(x => roll(20).then(y => roll(20).then(z => { console.log("three rolls:", x, y, z) return [x,y,z] } ) ) ) main().then(([x,y,z]) => x + y + z).then(sum => console.log("sum:", sum)).catch(console.error)

I will come back tomorrow to add an example that demonstrates errors and answer any questions if you have them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM