我向你保证一个奇迹

我向你保证一个奇迹

原文:https://medium.com/hackernoon/i-promise-you-a-miracle-396301751c4b

我正在滚动我自己的 JavaScript 承诺。

有时候,当你真的想理解一件事的时候,尝试自己去构建它是值得的。这就是我在这篇文章中对承诺所做的。我试图建立一个非常基本的“承诺”。

一开始有试镜…

让我们从最基本的开始,在任何人听说承诺之前,我们有过回调。看看下面的片段。

const testUrl = '[https://api.github.com/users/odemeulder](https://api.github.com/users/odemeulder)'
const fetchData = (url, callback) => {
  let xhr = new XMLHttpRequest()
  xhr.onreadystatechange = () => {
    if (xhr.readyState == 200 ) { 
      callback(xhr.responseText)
    }
  }
  xhr.open(url)
  xhr.send()
}
fetchData(testUrl, response => console.log(response) )

它是做什么的?它创建了一个非常简单的函数,该函数获取一个 url,使用XMLHttpRequest读取它,并允许回调函数对结果做一些事情。在最后一行,我们调用新函数。作为回调,我们提供了一个打印响应的函数。很简单。如果你对XMLHttpRequest不熟悉,不要苦恼,对于文章的其余部分理解并不重要。只知道它是浏览器提供给 JavaScript 执行http 请求的对象。这是一个异步函数,它在onreadystatechange 事件触发时接受回调。

承诺是什么样子的?

在开始写我们自己的承诺之前,让我们看看最终的结果应该是什么样的。什么是呼叫签名?

function fetchDataReturningPromise(url) {
  // what happens here?
}function functionWithDelay(arg) {
  // what happens here?
}fetchDataReturningPromise(url).then(response => console.log(response))
// or
functionWithDelay(arg1).then(performFollowUpOperation)
// or
functionWithDelay(arg1).then((resolvedValue) =>
  PerformFollowUpOperation(resolvedValue))

这是什么?让我们来关注第三个例子。

我们有一个延迟函数。函数内部的某个东西进行了异步调用。可能是超时、http 请求、数据库调用。

那个函数返回“某物”。那个某物有一个then 方法,所以‘某物’一定是一个对象。事实上,“某物”将是一个函数。

then 函数以函数或回调的形式接受一个参数。传递给then 函数的回调直到functionWithDelay 完成后才会执行。在我们的第三个例子中,传递给then的函数被标记为performFollowUpOperation。请注意,performFollowUpOperation接受一个参数(resolvedValue),该参数由functionWithDelay提供。这里有很多东西要打开。暂停,重读上一段(如果你愿意的话)。

总而言之:

  1. 用 next 函数定义一个函数。

2.定义一个具有延迟的函数,该函数返回一个具有下一个函数的函数(functionWithDelay)

3.带next 函数的函数需要带另一个函数作为参数。在延迟功能完成之前,不能执行该后续功能。(performFollowUpOperation)

4.具有延迟的函数必须以某种方式向后续函数传递一个值。

所以现在我们需要填充functionWithDelay,首先我们需要定义那个有next 功能的东西。

第一次尝试

让我们做第一次尝试。

 1: function FunctionWithAThenFunction() {
 2:  this.then = function (followUpFunction) {
 3:     followUpFunction()
 4:   }
 5: } 6: function functionWithDelay(param1) {
 7:   let xhr = new XMLHttpRequest()
 8:   xhr.onreadystatechange = () => {
 9:     if (xhr.readyState == 200) callback(xhr.responseText)
10:   }
11:   xhr.open(param1)
12:   xhr.send()
13:   return new FunctionWithAThenFunction()
14: }

好的,我们有一个带有then函数的函数。我们的functionWithDelaythen函数返回一个函数。酪 9 号线callback是什么?不知何故,T8 应该在回调中结束。这是不可能的。因此,followUpFunction 将立即执行,而不是等到functionWithDelay 完成。

我们如何让那个followUpFunction 成为callback

第二次尝试

我们来看看functionWithDelay。延迟函数的特征是什么?或者说异步函数的特征是什么?他们通常会以某种方式进行回调。因此,让我们试着将该函数重写为接受回调,这样我们就可以轻松地访问回调。

然后让我们重写我们的FunctionWithAThenFunction ,让它接受一个函数,更确切地说是一个带回调的异步函数。

1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){
 2:   let nextThingToDo
 3:   this.then = function ( followUpFunction) {
 4:     nextThingToDo = followUpFunction
 5:   }
 6:   aFnWithDelayAndACallback( nextThingToDo )
 7: }
 8: function functionWithDelay(param1) {
 9:   const httpRequestWithCallback = function (callback) {
10:     let xhr = XMLHttpRequest()
11:     xhr.onreadystatechange = () => {
12:       if (xhr.readyState == 200) callback(xhr.responseText)
13:     }
14:     xhr.open(param1)
15:     xhr.send()
16:   }
17:   return new FunctionWithAThenFunction(httpRequestWithCallback)
18: }
19: functionWithDelay('some argument').then(console.log)

第 1–7 行定义了我们的FunctionWithAThenFunction。这是延迟函数返回的函数。它需要一个函数作为参数。更确切地说是带有回调的函数。然后,then函数在一个局部变量中设置回调函数(nextThingToDo)。这个函数实际上做了一件事,那就是用回调函数调用这个函数,在我们的例子中是标记为aFnWithDelayAndACallback的函数。

第 8–18 行定义了我们的functionWithDelay。在我们的例子中,这个函数执行一个http 请求,并使用一个then函数返回一些东西。具有then功能的东西是FunctionWithAThenFunction的新实例。它看起来和我们上面做的有点不同。我们创建一个新的名为httpRequestWithCallback的本地临时函数,它接受一个回调参数,并包装前面示例中的整个XMLHttpRequest 代码。然后,这个新的局部函数作为参数传递给FunctionWithAThenFunction

现在,如果您尝试运行这个代码(第 19 行),它仍然不能完成我们想要做的事情。如果你继续下去,你会看到在我们有机会给它赋值之前,我们称之为nextThingToDo 的是undefined 。我们缺少一样东西,那就是某种地位。

第三次尝试

让我们添加一个跟踪执行状态的变量。

1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){
 2:   let nextThingToDo
 3:   let status = 'pending'
 4:   this.then = function ( followUpFunction) {
 5:     if (status === 'pending') {
 6:       nextThingToDo = followUpFunction
 7:     }
 8:     else {
 9:       followUpFunction()
10:     }
11:   }
12:   aFnWithDelayAndACallback( (newValue) => { 
13:      status = 'resolved'
14:      nextThingToDo(newValue)
15:   })
16: }
17: function functionWithDelay(param1) {
18:   const httpRequestWithCallback = function (callback) {
19:     let xhr = XMLHttpRequest()
20:     xhr.onreadystatechange = () => {
21:       if (xhr.readyState == 200) callback(xhr.responseText)
22:     }
23:     xhr.open(param1)
24:     xhr.send()
25:   }
26:   return new FunctionWithAThenFunction(httpRequestWithCallback)
27: }
28: functionWithDelay('some argument').then(console.log)

您会看到我们声明了一个status 变量(第 3 行)。这样我们就覆盖了所有的基础。如果在主函数完成之前调用了then,我们将followUpFunction 存储在一个变量中,如果没有,我们可以安全地执行它。但是它没有主函数的值。让我们解决这个问题。

1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){
 2:   let nextThingToDo, value
 3:   let status = 'pending'
 4:   this.then = function ( followUpFunction) {
 5:     if (status === 'pending') {
 6:       nextThingToDo = followUpFunction
 7:     }
 8:     else {
 9:       followUpFunction(value)
10:     }
11:   }
12:   aFnWithDelayAndACallback( (newValue) => { 
13:      status = 'resolved'
14:      value = newValue
15:      nextThingToDo(newValue)
16:   })
17: }
18: function functionWithDelay(param1) {
19:   const httpRequestWithCallback = function (callback) {
20:     let xhr = XMLHttpRequest()
21:     xhr.onreadystatechange = () => {
22:       if (xhr.readyState == 200) callback(xhr.responseText)
23:     }
24:     xhr.open(param1)
25:     xhr.send()
26:   }
27:   return new FunctionWithAThenFunction(httpRequestWithCallback)
28: }
29: functionWithDelay('some argument').then(console.log)

这里我们声明一个名为value的新局部变量。(第 2 行)当我们解析 main 函数时,我们设置这个值。因此它可以作为参数传递给第 9 行的followUpFunction

说到解决,为了清楚起见,让我们添加重写这个。与以前的示例相比,功能没有变化。

1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){
 2:   let nextThingToDo, value
 3:   let status = 'pending'
 4:   this.then = function ( followUpFunction) {
 5:     if (status === 'pending') {
 6:       nextThingToDo = followUpFunction
 7:     }
 8:     else {
 9:       followUpFunction(value)
10:     }
11:   }
12:   const resolve = newValue => {
13:     value = newValue
14:     status = 'resolved'
15:     if (nextThingToDo) nextThingToDo(newValue)
16:   }
17:   aFnWithDelayAndACallback(resolve)
17: }
18: function functionWithDelay(param1) {
19:   const httpRequestWithCallback = function (callback) {
20:     let xhr = XMLHttpRequest()
21:     xhr.onreadystatechange = () => {
22:       if (xhr.readyState == 200) callback(xhr.responseText)
23:     }
24:     xhr.open(param1)
25:     xhr.send()
26:   }
27:   return new FunctionWithAThenFunction(httpRequestWithCallback)
28: }
29: functionWithDelay('some argument').then(console.log)

这里,为了清楚起见,我们在第 12–16 行引入了局部解析函数。它随后在第 17 行被调用。

把所有的放在一起

我们已经走了很长一段路。让我们再写一遍上一个例子,并稍微改变一下函数名。

  • FunctionWithAThenFunction 变为Promise
  • functionWithDelay 变为fetchData
  • httpRequestWithCallback 变为httpRequest
  • aFnWithDelayAndACallback 成为fn
function Promise(fn){
   let nextThingToDo, value
   let status = 'pending'
   this.then = function ( callback ) {
     if (status === 'pending') {
       nextThingToDo = callback
     }
     else {
       callback(value)
     }
   }
   const resolve = newValue => {
     value = newValue
     status = 'resolved'
     if (nextThingToDo) nextThingToDo(newValue)
   }
   fn(resolve)
 }
function fetchData(url) {
  const httpRequest = function (callback) {
    let xhr = XMLHttpRequest()
    xhr.onreadystatechange = () => {
      if (xhr.readyState == 200) callback(xhr.responseText)
    }
    xhr.open(url)
    xhr.send()
  }
  return new Promise(httpRequest)
}const testUrl = '[https://api.github.com/users/odemeulder](https://api.github.com/users/odemeulder)'
fetchData(testUrl).then(console.log)

Promise 代码现在可用于其他功能。假设您想要一个类似于setTimeout 的函数来返回一个承诺。让我们调用函数Wait ,它将接受以毫秒为单位的延迟时间。

function Wait(milliSeconds) {
  const fn = (callback) => setTimeout(callback, milliSeconds)
  return new Promise(fn)
}
Wait(5000).then(() => console.log('we waited 5 seconds'))

或者假设您想在 Node 中使用httpRequest示例。在 Node XmlHttpRequest 中无法工作,因为那是浏览器提供的对象,你必须使用 Node 的内置https 模块。

const https = require('https')
const testUrl = '[https://api.github.com/users/odemeulder](https://api.github.com/users/odemeulder)'
function fetchDataForNode(url) {
  const fn = callback => {
    const options = {
      url: url,
      headers: { 'User-Agent': 'request' }
    }
    https.get(options, response => {
      let ret
      response.on('data', chunk => ret += chunk)
      response.on('end', () => callback(ret))
    })
  return new Promise(fn)
}
fetchDataForNode(testUrl).then(console.log)

同样,不要担心https.get在 node 中如何工作的技术细节。重要的部分是:创建一个接受回调并包装异步调用的函数,并将该函数传递给你的Promise 构造函数。

后续步骤

很明显,承诺远不止我们刚刚概述的内容。这篇文章已经够长了。但是这里有一些改进/补充。

一件事是then函数应该返回一个新的承诺。这让你可以连锁承诺。doSomething().then(somethingElse).then(aThirdThing)

另一件我们没有涉及的大事是错误处理。我们的Promise 函数也应该实现一个catch 函数。该函数将一个函数作为参数,如果有任何错误,将调用该函数。

结论

我们创建了一个非常基本的 promise 对象,我向您展示了如何创建一个返回 Promise 的函数的三个示例(fetchData 用于浏览器 http 请求,fetchDataForNode 用于 Node 中的 http 请求,wait)。在每种情况下,您创建一个接受回调的函数,创建一个新的Promise 函数并将该函数作为回调传递。

我欢迎任何反馈和掌声。


本站为非盈利网站,作品由网友提供上传,如无意中有侵犯您的版权,请联系删除