노드를 운영하고 콜백 함수 대신 Promise를 사용하는 방법

php中世界最好的语言
풀어 주다: 2018-05-31 10:14:03
원래의
1198명이 탐색했습니다.

이번에는 노드에서 콜백 함수 대신 Promise를 사용하는 방법을 알려드리겠습니다. 노드에서 콜백 함수 대신 Promise를 사용할 때 주의사항은 무엇인가요?

async의 핵심은

프로세스 제어입니다. 실제로 비동기 프로그래밍에는 Promise/Deferred 모델이라는 좀 더 고전적인 모델이 있습니다(물론 eventproxy, co 등과 같은 더 많은 관련 솔루션이 있으며 때가 되면 구멍을 파보겠습니다)

먼저 , 생각해보자 일반적인 비동기 프로그래밍 모델에서 다음 주제를 고려하십시오. 파일 읽기, 파일 내용을 콘솔에 출력

var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
  console.log(data);
});
로그인 후 복사
매우 간단해 보입니다. 한 단계 더 나아가십시오. 두 파일을 읽고 콘솔에 두 파일의 내용을 출력하십시오. 콘솔

var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
  console.log(data);
  fs.readFile('2.txt', 'utf8', function (err, data) {
    console.log(data);
  });
});
로그인 후 복사
더 많은 파일을 읽으면 어떨까요

var fs = require('fs');
fs.readFile('1.txt', 'utf8', function (err, data) {
  fs.readFile('2.txt', 'utf8', function (err, data) {
    fs.readFile('3.txt', 'utf8', function (err, data) {
      fs.readFile('4.txt', 'utf8', function (err, data) {
        // ...
      });
    });
  });
});
로그인 후 복사
이것은 전설적인 콜백 지옥입니다. 비동기를 사용하여 이 코드를 개선할 수 있지만 이 경우 개선을 위해 promise/defer를 사용해야 합니다

promise 기본 개념

우선 일반 자바스크립트 객체와 다르지 않은 객체임과 동시에, 비동기 작업의 최종 결과를 나타내는 통일된 인터페이스에 동의하는 사양이기도 합니다. 동기화를 위한 비동기 작업 작업을 비동기적으로 수행하지만 프로그램 실행 순서는 동기식으로 코드를 작성하는 것

1. 약속에는 완료되지 않음, 이행됨, 거부됨이라는 세 가지 상태만 있습니다

2. 불완전에서 완료로 또는 불완전에서 실패로 변환

3. Promise의 상태 전환은 한 번만 발생합니다

promise에는 then 메소드가 있으며 then 메소드는 3개의 함수를 매개변수로 받을 수 있습니다. 처음 두 함수는 이행 및 거부라는 두 가지 약속 상태의 콜백 함수에 해당합니다. 세 번째 함수는 진행 정보를 처리하는 데 사용됩니다

이를 이해하려면 몇 가지 중요한 원칙을 기억해야 합니다. .then()은 항상 다음 코드와 같은 새 Promise를 반환합니다.

var promise = readFile()
var promise2 = promise.then(readAnotherFile, console.error)
로그인 후 복사
여기서 then의 매개 변수는 readAnotherFile입니다. , console.error 비동기 작업이 성공한 후의 onFulfilled 액션 또는 실패 후의 OnRejected 액션을 나타냅니다. 즉, 파일을 성공적으로 읽은 후에 readAnotherFile 함수가 실행되고, 그렇지 않으면 실패 시 오류가 인쇄되어 기록됩니다. 이 구현은 두 가지 가능성 중 하나일 뿐입니다.

다음과 같이도 이해할 수 있습니다.

promiseSomething().then(function (fulfilled) {
  // 当 promise 状态变成 fulfilled 时,调用此函数
}, function (rejected) {
  // 当 promise 状态变成 rejected 时,调用此函数
}, function (progress) {
  // 当返回进度信息时,调用此函数
});
로그인 후 복사
Promise 법칙에서 분리해야 하는 두 부분이 있습니다.

1 then()은 호출할 때마다 항상 새로운 Promise를 반환합니다. , 콜백이 호출되기 전에 .then()이 약속을 제공했기 때문에 콜백이 수행하는 작업은 중요하지 않습니다. 콜백의 동작은 약속의 구현에만 영향을 미칩니다. 값이 이 Promise의 구현된 값을 이 값으로 반환하는 Promise인 경우 콜백에서 오류가 발생하면 Promise는 오류를 거부합니다

2. 새로운 Promise는 호출되는 Promise의 .then()과 다릅니다. Promise의 긴 체인은 때때로 .then()이 호출될 때마다 새로운 Promise가 생성된다는 사실을 숨깁니다. 실제로 고려해야 할 것은 마지막 호출인 then()이 실패를 나타낼 수 있으므로 이 실패를 포착하지 않으면 쉽게 오류 예외가 사라질 것입니다.

q를 사용하여 간단한 예를 살펴보겠습니다. 이 문제를 처리하세요:

var Q = require('q');
var defer = Q.defer();
/**
 * 获取初始 promise
 * @private
 */
function getInitialPromise() {
  return defer.promise;
}
 
/**
 * 为 promise 设置三种状态的回调函数
 */
getInitialPromise().then(function (success) {
  console.log(success);
}, function (error) {
  console.log(error);
}, function (progress) {
  console.log(progress);
});
defer.notify('in progress'); // 控制台打印 in progress
defer.resolve('resolve');   // 控制台打印 resolve
defer.reject('reject');    // 没有输出。promise 的状态只能改变一次
로그인 후 복사

Promise 전달

Then 메소드는 Promise를 반환합니다. 다음 예에서는 outputPromise를 사용하여 그때 반환된 Promise를 가리킵니다.

var outputPromise = getInputPromise().then(function (fulfilled) {
 
  }, function (rejected) {
 
  });
로그인 후 복사
이제 출력Promise는 함수(완료) 또는 함수(거부)에 의해 제어되는 약속이 됩니다. 직접적인 의미는 function(fulfilled) 또는 function(rejected)가 문자열, 배열, 객체 등과 같은 값을 반환하면 outputPromise의 상태가 이행된다는 것입니다.

다음 예에서는 defer.resovle()을 통해 inputPromise 상태를 Fulfuled로 변경하면 콘솔 출력이 Fulpleed되는 것을 볼 수 있습니다.

defer.reject()를 통해 inputPromise의 상태를 Fulfilled로 변경하면 거부되고 콘솔 출력도 거부됩니다

var Q = require('q');
var defer = Q.defer();
/**
 * 通过 defer 获得 promise
 * @private
 */
function getInputPromise() {
  return defer.promise;
}
 
/**
 * 当 inputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)
 * 当 inputPromise 状态由未完成变成 rejected 时,调用 function(rejected)
 * 将 then 返回的 promise 赋给 outputPromise
 * function(fulfilled) 和 function(rejected) 通过返回字符串将 outputPromise 的状态由
 * 未完成改变为 fulfilled
 * @private
 */
var outputPromise = getInputPromise().then(function (fulfilled) {
  return 'fulfilled';
}, function (rejected) {
  return 'rejected';
});
 
/**
 * 当 outputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled),控制台打印 'fulfilled: fulfilled'。
 * 当 outputPromise 状态由未完成变成 rejected, 调用 function(rejected), 控制台打印 'rejected: rejected'。
 */
outputPromise.then(function (fulfilled) {
  console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
  console.log('rejected: ' + rejected);
});
 
/**
 * 将 inputPromise 的状态由未完成变成 rejected
 */
defer.reject(); // 输出 fulfilled: rejected
 
/**
 * 将 inputPromise 的状态由未完成变成 fulfilled
 */
//defer.resolve(); // 输出 fulfilled: fulfilled
로그인 후 복사

当 function(fulfilled) 或者 function(rejected) 抛出异常时,那么 outputPromise 的状态就会变成 rejected

var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
 
/**
 * 通过 defer 获得 promise
 * @private
 */
function getInputPromise() {
  return defer.promise;
}
 
/**
 * 当 inputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)
 * 当 inputPromise 状态由未完成变成 rejected 时,调用 function(rejected)
 * 将 then 返回的 promise 赋给 outputPromise
 * function(fulfilled) 和 function(rejected) 通过抛出异常将 outputPromise 的状态由
 * 未完成改变为 reject
 * @private
 */
var outputPromise = getInputPromise().then(function (fulfilled) {
  throw new Error('fulfilled');
}, function (rejected) {
  throw new Error('rejected');
});
 
/**
 * 当 outputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)。
 * 当 outputPromise 状态由未完成变成 rejected, 调用 function(rejected)。
 */
outputPromise.then(function (fulfilled) {
  console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
  console.log('rejected: ' + rejected);
});
 
/**
 * 将 inputPromise 的状态由未完成变成 rejected
 */
defer.reject();   // 控制台打印 rejected [Error:rejected]
 
/**
 * 将 inputPromise 的状态由未完成变成 fulfilled
 */
//defer.resolve(); // 控制台打印 rejected [Error:fulfilled]
로그인 후 복사

当 function(fulfilled) 或者 function(rejected) 返回一个 promise 时,outputPromise 就会成为这个新的 promise.

这样做的意义在于聚合结果 (Q.all),管理延时,异常恢复等等

比如说我们想要读取一个文件的内容,然后把这些内容打印出来。可能会写出这样的代码:

// 错误的写法
var outputPromise = getInputPromise().then(function (fulfilled) {
  fs.readFile('test.txt', 'utf8', function (err, data) {
    return data;
  });
});
로그인 후 복사

然而这样写是错误的,因为 function(fulfilled) 并没有返回任何值。需要下面的方式:

var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
 
/**
 * 通过 defer 获得promise
 * @private
 */
function getInputPromise() {
  return defer.promise;
}
 
/**
 * 当 inputPromise 状态由未完成变成 fulfil时,调用 function(fulfilled)
 * 当 inputPromise 状态由未完成变成 rejected时,调用 function(rejected)
 * 将 then 返回的 promise 赋给 outputPromise
 * function(fulfilled) 将新的 promise 赋给 outputPromise
 * 未完成改变为 reject
 * @private
 */
var outputPromise = getInputPromise().then(function (fulfilled) {
  var myDefer = Q.defer();
  fs.readFile('test.txt', 'utf8', function (err, data) {
    if (!err && data) {
      myDefer.resolve(data);
    }
  });
  return myDefer.promise;
}, function (rejected) {
  throw new Error('rejected');
});
 
/**
 * 当 outputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled),控制台打印 test.txt 文件内容。
 *
 */
outputPromise.then(function (fulfilled) {
  console.log(fulfilled);
}, function (rejected) {
  console.log(rejected);
});
 
/**
 * 将 inputPromise 的状态由未完成变成 rejected
 */
//defer.reject();
 
/**
 * 将 inputPromise 的状态由未完成变成 fulfilled
 */
defer.resolve(); // 控制台打印出 test.txt 的内容
로그인 후 복사

方法传递

方法传递有些类似于 Java 中的 try 和 catch。当一个异常没有响应的捕获时,这个异常会接着往下传递

方法传递的含义是当一个状态没有响应的回调函数,就会沿着 then 往下找

没有提供 function(rejected)

var outputPromise = getInputPromise().then(function (fulfilled) { })
로그인 후 복사

如果 inputPromise 的状态由未完成变成 rejected, 此时对 rejected 的处理会由 outputPromise 来完成

var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
 * 通过defer获得promise
 * @private
 */
function getInputPromise() {
  return defer.promise;
}
 
/**
 * 当 inputPromise 状态由未完成变成 fulfil 时,调用 function(fulfilled)
 * 当 inputPromise 状态由未完成变成 rejected 时,这个 rejected 会传向 outputPromise
 */
var outputPromise = getInputPromise().then(function (fulfilled) {
  return 'fulfilled'
});
outputPromise.then(function (fulfilled) {
  console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
  console.log('rejected: ' + rejected);
});
 
/**
 * 将 inputPromise 的状态由未完成变成 rejected
 */
defer.reject('inputpromise rejected'); // 控制台打印 rejected: inputpromise rejected
 
/**
 * 将 inputPromise的状态由未完成变成fulfilled
 */
//defer.resolve();
로그인 후 복사

没有提供 function(fulfilled)

var outputPromise = getInputPromise().then(null, function (rejected) { })
로그인 후 복사

如果 inputPromise 的状态由未完成变成 fulfilled, 此时对 fulfil 的处理会由 outputPromise 来完成

var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
 
/**
 * 通过defer获得promise
 * @private
 */
function getInputPromise() {
  return defer.promise;
}
 
/**
 * 当 inputPromise 状态由未完成变成 fulfil时,传递给 outputPromise
 * 当 inputPromise 状态由未完成变成 rejected时,调用 function(rejected)
 * function(fulfilled) 将新的 promise 赋给 outputPromise
 * 未完成改变为 reject
 * @private
 */
var outputPromise = getInputPromise().then(null, function (rejected) {
  return 'rejected';
});
 
outputPromise.then(function (fulfilled) {
  console.log('fulfilled: ' + fulfilled);
}, function (rejected) {
  console.log('rejected: ' + rejected);
});
 
/**
 * 将 inputPromise 的状态由未完成变成 rejected
 */
// defer.reject('inputpromise rejected');
 
/**
 * 将 inputPromise 的状态由未完成变成fulfilled
 */
defer.resolve('inputpromise fulfilled'); // 控制台打印fulfilled: inputpromise fulfilled
로그인 후 복사

可以使用 fail(function(error)) 来专门针对错误处理,而不是使用 then(null,function(error))

var outputPromise = getInputPromise().fail(function (error) { })
로그인 후 복사

看这个例子:

var Q = require('q');
var fs = require('fs');
var defer = Q.defer();
/**
 * 通过defer获得promise
 * @private
 */
function getInputPromise() {
  return defer.promise;
}
 
/**
 * 当 inputPromise 状态由未完成变成 fulfil 时,调用 then(function(fulfilled))
 * 当 inputPromise 状态由未完成变成 rejected 时,调用 fail(function(error))
 * function(fulfilled) 将新的 promise 赋给 outputPromise
 * 未完成改变为reject
 * @private
 */
var outputPromise = getInputPromise().then(function (fulfilled) {
  return fulfilled;
}).fail(function (error) {
  console.log('fail: ' + error);
});
/**
 * 将 inputPromise 的状态由未完成变成 rejected
 */
defer.reject('inputpromise rejected');// 控制台打印 fail: inputpromise rejected
 
/**
 * 将 inputPromise 的状态由未完成变成 fulfilled
 */
//defer.resolve('inputpromise fulfilled');
로그인 후 복사

可以使用 progress(function (progress)) 来专门针对进度信息进行处理,而不是使用 then(function (success) { }, function (error) { }, function (progress) { })

var Q = require('q');
var defer = Q.defer();
/**
 * 获取初始 promise
 * @private
 */
function getInitialPromise() {
  return defer.promise;
}
/**
 * 为 promise 设置 progress 信息处理函数
 */
var outputPromise = getInitialPromise().then(function (success) {
 
}).progress(function (progress) {
  console.log(progress);
});
 
defer.notify(1);
defer.notify(2); // 控制台打印 1,2
로그인 후 복사

promise 链

promise 链提供了一种让函数顺序执行的方法

函数顺序执行是很重要的一个功能。比如知道用户名,需要根据用户名从数据库中找到相应的用户,然后将用户信息传给下一个函数进行处理

var Q = require('q');
var defer = Q.defer();
// 一个模拟数据库
var users = [{ 'name': 'andrew', 'passwd': 'password' }];
function getUsername() {
  return defer.promise;
}
 
function getUser(username) {
  var user;
  users.forEach(function (element) {
    if (element.name === username) {
      user = element;
    }
  });
  return user;
}
 
// promise 链
getUsername().then(function (username) {
  return getUser(username);
}).then(function (user) {
  console.log(user);
});
 
defer.resolve('andrew');
로그인 후 복사

我们通过两个 then 达到让函数顺序执行的目的。

then 的数量其实是没有限制的。当然,then 的数量过多,要手动把他们链接起来是很麻烦的。比如

foo(initialVal).then(bar).then(baz).then(qux)
로그인 후 복사

这时我们需要用代码来动态制造 promise 链

var funcs = [foo, bar, baz, qux]
var result = Q(initialVal) 
funcs.forEach(function (func) {
  result = result.then(func)
})
return result
로그인 후 복사

当然,我们可以再简洁一点

var funcs = [foo, bar, baz, qux]
funcs.reduce(function (pre, current),Q(initialVal){
  return pre.then(current)
})
로그인 후 복사

看一个具体的例子

function foo(result) {
  console.log(result);
  return result + result;
}
 
// 手动链接
Q('hello').then(foo).then(foo).then(foo);
 
// 控制台输出: hello
// hellohello
// hellohellohello
 
// 动态链接
var funcs = [foo, foo, foo];
var result = Q('hello');
 
funcs.forEach(function (func) {
  result = result.then(func);
});
 
// 精简后的动态链接
funcs.reduce(function (prev, current) {
  return prev.then(current);
}, Q('hello'));
로그인 후 복사

对于 promise 链,最重要的是需要理解为什么这个链能够顺序执行。如果能够理解这点,那么以后自己写 promise 链可以说是轻车熟路啊

promise 组合

回到我们一开始读取文件内容的例子。如果现在让我们把它改写成 promise 链,是不是很简单呢?

var Q = require('q'),
  fs = require('fs');
 
function printFileContent(fileName) {
  return function () {
    var defer = Q.defer();
    fs.readFile(fileName, 'utf8', function (err, data) {
      if (!err && data) {
        console.log(data);
        defer.resolve();
      }
    })
    return defer.promise;
  }
}
 
// 手动链接
printFileContent('sample01.txt')()
  .then(printFileContent('sample02.txt'))
  .then(printFileContent('sample03.txt'))
  .then(printFileContent('sample04.txt'));  // 控制台顺序打印 sample01 到 sample04 的内容
로그인 후 복사

很有成就感是不是。然而如果仔细分析,我们会发现为什么要他们顺序执行呢,如果他们能够并行执行不是更好吗? 我们只需要在他们都执行完成之后,得到他们的执行结果就可以了

我们可以通过 Q.all([promise1,promise2...]) 将多个 promise 组合成一个 promise 返回。 注意:

1. 当 all 里面所有的 promise 都 fulfil 时,Q.all 返回的 promise 状态变成 fulfil

2. 当任意一个 promise 被 reject 时,Q.all 返回的 promise 状态立即变成 reject

我们来把上面读取文件内容的例子改成并行执行吧

var Q = require('q');
var fs = require('fs');
/**
 *读取文件内容
 *@private
 */
function printFileContent(fileName) {
  // Todo: 这段代码不够简洁。可以使用 Q.denodeify 来简化
  var defer = Q.defer();
 
  fs.readFile(fileName, 'utf8', function (err, data) {
    if (!err && data) {
      console.log(data);
      defer.resolve(fileName + ' success ');
    } else {
      defer.reject(fileName + ' fail ');
    }
  })
 
  return defer.promise;
 
}
 
Q.all([printFileContent('sample01.txt'), printFileContent('sample02.txt'), printFileContent('sample03.txt'), printFileContent('sample04.txt')])
  .then(function (success) {
    console.log(success);
  }); // 控制台打印各个文件内容 顺序不一定
로그인 후 복사

现在知道 Q.all 会在任意一个 promise 进入 reject 状态后立即进入 reject 状态。如果我们需要等到所有的 promise 都发生状态后(有的 fulfil, 有的 reject),再转换 Q.all 的状态, 这时我们可以使用 Q.allSettled

var Q = require('q'),
  fs = require('fs');
/**
 *读取文件内容
 *@private
 */
 
function printFileContent(fileName) {
 
  // Todo: 这段代码不够简洁。可以使用Q.denodeify来简化
  var defer = Q.defer();
 
  fs.readFile(fileName, 'utf8', function (err, data) {
    if (!err && data) {
      console.log(data);
      defer.resolve(fileName + ' success ');
    } else {
      defer.reject(fileName + ' fail ');
    }
  })
 
  return defer.promise;
   
}
 
Q.allSettled([printFileContent('nosuchfile.txt'), printFileContent('sample02.txt'), printFileContent('sample03.txt'), printFileContent('sample04.txt')])
  .then(function (results) {
    results.forEach(
      function (result) {
        console.log(result.state);
      }
    );
  });
로그인 후 복사

结束 promise 链

通常,对于一个 promise 链,有两种结束的方式。第一种方式是返回最后一个 promise

如 return foo().then(bar);

第二种方式就是通过 done 来结束 promise 链

如 foo().then(bar).done()

为什么需要通过 done 来结束一个 promise 链呢? 如果在我们的链中有错误没有被处理,那么在一个正确结束的 promise 链中,这个没被处理的错误会通过异常抛出

var Q = require('q'); 
function getPromise(msg, timeout, opt) { 
  var defer = Q.defer(); 
  setTimeout(function () {
    console.log(msg);
    if (opt)
      defer.reject(msg);
    else
      defer.resolve(msg);
  }, timeout); 
  return defer.promise; 
}
 
/**
 * 没有用 done() 结束的 promise 链
 * 由于 getPromse('2',2000,'opt') 返回 rejected, getPromise('3',1000) 就没有执行
 * 然后这个异常并没有任何提醒,是一个潜在的 bug
 */
getPromise('1', 3000)
  .then(function () { return getPromise('2', 2000, 'opt') })
  .then(function () { return getPromise('3', 1000) });
 
/**
 * 用 done() 结束的 promise 链
 * 有异常抛出
 */
getPromise('1', 3000)
  .then(function () { return getPromise('2', 2000, 'opt') })
  .then(function () { return getPromise('3', 1000) })
  .done();
로그인 후 복사

附录:一个 Promise 简单的应用(Node.js笔记(5)promise)

附:Promises/A+ 规范

promise 代表一个异步操作的最终结果。主要通过 promise 的 then 方法订阅其最终结果的处理回调函数,和订阅因某原因无法成功获取最终结果的处理回调函数。

更对详细见:Promises/A+

A 与 A+ 的不同点

  1. A+ 规范通过术语 thenable 来区分 promise 对象

  2. A+ 定义 onFulfilled/onRejectd 必须是作为函数来调用,而且调用过程必须是异步的

  3. A+ 严格定义了 then 方法链式调用时,onFulfilled/onRejectd 的调用顺序

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

Vue微信项目按需授权登录实战案例详解

怎样使用angularjs做出购物金额计算工具

위 내용은 노드를 운영하고 콜백 함수 대신 Promise를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!