1

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);
});

2
  • 1
    There are all sorts of things wrong here, .then() can be passed two callbacks. You don't have a .catch() method. Inside of .then(), you would not use the executor callback function again when you construct a new promise. That is called once and only once when the original promise is created. reject() takes a reason. Commented May 12, 2021 at 3:56
  • Also don't catch errors when you call the executor (called fn in your case) Commented May 12, 2021 at 4:04

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.